当前位置:   article > 正文

Apollo安装、springboot集成Apollo、Apollo动态刷新、Apollo集成sentinel_springboot连接apollo

springboot连接apollo

1.Apollo简介

1.1 简介

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用的不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。

Apollo包括服务端和客户端两部分:

服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。

Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。

github:https://github.com/ctripcorp/apollo

1.2 Apollo特性

基于配置的特殊性,所以Apollo从设计之初就立志于成为一个有治理能力的配置发布平台,目前提供了以下的特性:

统一管理不同环境、不同集群的配置
    提供了一个统一界面集中式管理不同环境(environment)、不同集群(cluster)、不同命名空间(namespace)的配置。
    同一份代码部署在不同的集群,可以有不同的配置,比如zookeeper的地址等
    通过命名空间(namespace)可以很方便地支持多个不同应用共享同一份配置,同时还允许应用对共享的配置进行覆盖

配置修改实时生效(热发布)
    用户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应用程序

版本发布管理
    所有的配置发布都有版本概念,从而可以方便地支持配置的回滚

灰度发布
    支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例

权限管理、发布审核、操作审计
    应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误。
    所有的操作都有审计日志,可以方便地追踪问题

客户端配置信息监控
    可以在界面上方便地看到配置在被哪些实例使用

提供Java和.Net原生客户端
    提供了Java和.Net的原生客户端,方便应用集成
    支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要Spring 3.1.1+)
    同时提供了Http接口,非Java和.Net应用也可以方便地使用

提供开放平台API
    Apollo自身提供了比较完善的统一配置管理界面,支持多环境、多数据中心配置管理、权限、流程治理等特性。不过Apollo出于通用性考虑,不会对配置的修改做过多限制,只要符合基本的格式就能保存,不会针对不同的配置值进行针对性的校验,如数据库用户名、密码,Redis服务地址等
    对于这类应用配置,Apollo支持应用方通过开放平台API在Apollo进行配置的修改和发布,并且具备完善的授权和权限控制
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
1.3 执行流程

操作流程如下:

在这里插入图片描述1)、在Apollo配置中心修改配置

2)、应用程序通过Apollo客户端从配置中心拉取配置信息

用户通过Apollo配置中心修改或发布配置后,会有两种机制来保证应用程序来获取最新配置:一种是Apollo配置中心会向客户端推送最新的配置;另外一种是Apollo客户端会定时从Apollo配置中心拉取最新的配置,通过以上两种机制共同来保证应用程序能及时获取到配置。

1.4 安装Apollo
1.4.1 环境依赖

运行时环境:

Java
Apollo服务端:1.8+
Apollo客户端:1.7+
由于需要同时运行服务端和客户端,所以建议安装Java 1.8+。

MySQL
版本要求:5.6.5+
Apollo的表结构对timestamp使用了多个default声明,所以需要5.6.5以上版本。

1.4.2 下载

访问Apollo的官方主页获取安装包(本次使用1.3版本):https://github.com/ctripcorp/apollo/tags

打开1.3发布链接,下载必须的安装包:https://github.com/ctripcorp/apollo/releases/tag/v1.3.0
在这里插入图片描述解压安装包后将apollo-configservice-1.3.0.jar, apollo-adminservice-1.3.0.jar, apollo-portal-1.3.0.jar放置于apollo目录下

1.4.3 创建数据库

Apollo服务端共需要两个数据库:ApolloPortalDB和ApolloConfigDB,ApolloPortalDB只需要在生产环境部署一个即可,而ApolloConfigDB需要在每个环境部署一套。

a)创建ApolloPortalDB,sql脚本下载地址:https://github.com/ctripcorp/apollo/blob/v1.3.0/scripts/db/migration/configdb/V1.0.0__initialization.sql

以MySQL原生客户端为例:source apollo/ApolloPortalDB__initialization.sql

验证ApolloPortalDB:导入成功后,可以通过执行以下sql语句来验证:
  • 1
  • 2
  • 3
  • 4
  • 5

select Id, Key, Value, Comment from ApolloPortalDB.ServerConfig limit 1;

注:ApolloPortalDB只需要在生产环境部署一个即可

b)创建ApolloConfigDB,sql脚本下载地址:https://github.com/ctripcorp/apollo/blob/v1.3.0/scripts/db/migration/configdb/V1.0.0__initialization.sql

以MySQL原生客户端为例: source apollo/ApolloConfigDB__initialization.sql
验证ApolloConfigDB:导入成功后,可以通过执行以下sql语句来验证:
select `Id`, `Key`, `Value`, `Comment` from `ApolloConfigDB`.`ServerConfig` limit 1;
  • 1
  • 2
  • 3
  • 4
  • 5
1.4.4启动Apollo

在bin目录下创建批量启动脚本runApollo.bat

-Dserver.port=7070 修改默认端口
configService:7070, adminService:7080, ApolloPortal:7090
注意修改ApolloPortal中的 -Ddev_meta=http://localhost:7070

echo

set url="localhost"
set username="root"
set password="root"

start "configService" java -Xms256m -Xmx256m -Dserver.port=7070 -Dapollo_profile=github -Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDB?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-configservice.log -jar .\apollo-configservice-1.3.0.jar
start "adminService" java -Xms256m -Xmx256m -Dserver.port=7080 -Dapollo_profile=github -Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDB?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-adminservice.log -jar .\apollo-adminservice-1.3.0.jar
start "ApolloPortal" java -Xms256m -Xmx256m -Dapollo_profile=github,auth -Ddev_meta=http://localhost:7070/ -Dserver.port=7090 -Dspring.datasource.url=jdbc:mysql://%url%/ApolloPortalDB?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-portal.log -jar .\apollo-portal-1.3.0.jar
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

**修改ApolloConfigDB库中的ServerConfig表,将地址修改为configService的地址。**这步很重要,否则启动会一直报错
在这里插入图片描述

1.4.5 进入Apollo控制台

访问ApolloPortal的地址:http://localhost:7090/ 账号默认为apollo admin
创建apollo-config-center-6100
在这里插入图片描述

2.springboot集成Apollo

2.1 新建Moduleapollo-config-center-6100
2.2 改pom
<dependencies>
        <!--web-actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-context</artifactId>
            <version>2.2.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-apollo</artifactId>
            <version>1.6.3</version>
        </dependency>
    </dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
2.3 改yml
server:
  port: 6100

app:
  id: apollo-config-center-6100 # 应用的身份信息,同apollo后台配置的AppId
apollo:
  meta: http://127.0.0.1:7070
  cacheDir: /opt/data/apollo‐config #  指定配置文件的本地缓存路径
  bootstrap:
    enabled: true # 在应用启动阶段,向spring容器注入被托管的application.properties文件的配置信息
    eagerLoad:
      enabled: true # 将apollo配置加载提前到初始化日志系统之前
    namespaces: application # 指定namespace

dev:
  meta: http://localhost:7070\

env: DEV

spring:
  application:
    name: apollo-config-center-6100
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
2.4 启动类和业务类

启动类:添加 @EnableApolloConfig 注解启用Apollo

@SpringBootApplication
@EnableApolloConfig
@Slf4j
public class ApolloMain6100 {
    public static void main(String[] args) {
        SpringApplication.run(ApolloMain6100.class,args);
        log.info("==========ApolloMain6100启动成功===============");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

业务类:

@RestController
@Slf4j
public class ApolloController {

    @Value("${test.url}")
    private String url;

    @Resource
    private SystemConfig systemConfig;

    @GetMapping("/test/apollo")
    public String getUrl(){
        log.info("url:"+url);
        log.info("systemConfig.name:"+systemConfig.getName());
        return systemConfig.getName();
    }

    @GetMapping("/testB")
    @SentinelResource(value = "testB",fallbackClass = FlowLimitFallback.class,fallback = "testB")
    public String testB() {
//        int a = 10/0;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "------testB";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
@ConfigurationProperties(prefix = "system")
@Component
@Data
public class SystemConfig {
    private String name;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
public class FlowLimitFallback {

    public static String testB() {
        return "服务不可用";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
2.5 apollo动态刷新配置

test.url = http://localhost:6662
修改其值,发现可以动态更新。

但是对于bean中的值,apollo无法直接做到动态更新。需要新增配置类。
导入springcloud的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-context</artifactId>
    <version>2.2.7.RELEASE</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
@Slf4j
@Component
public class ApolloConfigChanged implements ApplicationContextAware {

    @Resource
    private ApplicationContext applicationContext;

    @ApolloConfigChangeListener("application.properties")
    private void apolloChangedHandler(ConfigChangeEvent event){
        Set<String> set = event.changedKeys();
        for (String key : set) {
            ConfigChange change = event.getChange(key);
            log.info("application.properties changed:"+change.toString());
            this.applicationContext.publishEvent(new EnvironmentChangeEvent(set));
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
2.6 apollo集成sentinel

导入依赖:
血泪教训:sentinel-datasource-apollo的版本需要和spring-cloud-starter-alibaba-sentinel中对应jar包版本一致,否则可能导致流控规则无法注入。

			<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-apollo</artifactId>
            <version>1.6.3</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述新增配置类,在项目启动的时候将规则注入

@Component
@Slf4j
public class ApolloSentinelRuleConfig implements InitializingBean {

    @ApolloConfig
    private Config config;

    @Override
    public void afterPropertiesSet() throws Exception {
//        System.setProperty("csp.sentinel.log.dir","logs/csp/");
        String namespace = "application.properties";
        Set<String> propertyNames = config.getPropertyNames();
        for (String propertyName : propertyNames) {
            if(propertyName.contains("sentinel")){
                log.info("限流规则发生改变:"+propertyName);
                String[] split = propertyName.split("\\.");
                loadParam(namespace,split[2],propertyName);
            }
        }
    }

    private void loadParam(String namespace,String ruleType,String ruleKey){
        switch (ruleType){
            case "flow":
                ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new ApolloDataSource<>(namespace,
                        ruleKey, "[]", source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
                }));
                log.info("namespace:{},rule:{}",namespace,flowRuleDataSource.getProperty().toString());
                FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
                break;
            case "degrade":
                ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new ApolloDataSource<>(namespace,
                        ruleKey, "[]", source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
                }));
                log.info("namespace:{},rule:{}",namespace,degradeRuleDataSource.getProperty());
                DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
                break;
            case "param":
                ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleDataSource = new ApolloDataSource<>(namespace,
                        ruleKey, "[]", source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {
                }));
                log.info("namespace:{},rule:{}",namespace,paramFlowRuleDataSource.getProperty());
                ParamFlowRuleManager.register2Property(paramFlowRuleDataSource.getProperty());
                break;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

以上实现参考官方文档:https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95
在这里插入图片描述在apollo新增配置:
sentinel.rule.flow = [{“resource”: “testB”,“grade”: 1,“count”: 2}]

2.7测试

启动apollo,启动sentinel,启动6100。
登录sentinel后台,请求http://localhost:6100/testB,刷新sentinel,发现限流规则已经注入。快速刷新testB,发现流控生效。去apollo后台更新流控规则,发现sentinel后台也同步更新。

2.8 集群配置(通过系统参数传递进去)

应用场景:Apollo是基于AppID来区分不同实例配置,那如何在不改变AppID的情况下使用不同的配置实例呢?那就可以使用Apollo集群

目前需求:通过docker搭建一个项目的多个实例,而项目的配置是依赖于Apollo的,在不改项目AppID的情况下使得每个实例使用不同的Apollo配置。

配置步骤:

1)登录Apollo配置页面,添加集群

添加一个集群,eg:test1 然后选择项目环境,eg:pro

2)修改项目 server.properties 配置文件,添加如下配置

env=pro
idc=test1

使用pro环境,集群为test1,这样此项目实例就会去读取test1集群的配置

sudo docker run -dit -m 3G --net=host --privileged=true --cap-add=SYS_PTRACE --name=study-spring-test -v /usr/java/jdk1.8.0_191:/usr/java/jdk1.8.0_191 -v /TEST/study-spring-test/logs:/TEST/study-spring-test/logs -v /opt:/opt -e JAVA_MAJOR_VERSION=8 -e JAVA_INIT_MEM_RATIO=25 -e ENV= -e APOLLO_META= -e IDC=test1 docker.study.net/test/study-spring-test:TSET_V9.1.24_20200807_B01;

3. Apollo工作原理

3.1 各模块职责

在这里插入图片描述
上图简要描述了Apollo的总体设计,我们可以从下往上看:

Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端

Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)

Eureka提供服务注册和发现,为了简单起见,目前Eureka在部署时和Config Service是在一个JVM进程中的

Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳

在Eureka之上架了一层Meta Server用于封装Eureka的服务发现接口

Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务, 同时在Client侧会做load balance、错误重试

Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务, 同时在Portal侧会做load balance、错误重试

为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程 中

3.2 分步执行流程

1)Apollo启动后,Config/Admin Service会自动注册到Eureka服务注册中心,并定期发送保活心跳。

  1. Apollo Client和Portal管理端通过配置的Meta Server的域名地址经由Software Load Balancer(软件负载均衡器)进行负载均衡后分配到某一个Meta Server

  2. Meta Server从Eureka获取Config Service和Admin Service的服务信息,相当于是一个Eureka Client

4.)Meta Server获取Config Service和Admin Service(IP+Port)失败后会进行重试

  1. 获取到正确的Config Service和Admin Service的服务信息后,Apollo Client通过Config Service为应用提供配置获取、实时更新等功能;Apollo Portal管理端通过Admin Service提供配置新增、修改、发布等功能
3.3 核心概念

1)application (应用) 这个很好理解,就是实际使用配置的应用,Apollo客户端在运行时需要知道当前应用是谁,从而可以去获取 对应的配置 关键字:appId

2)environment (环境) 配置对应的环境,Apollo客户端在运行时需要知道当前应用处于哪个环境,从而可以去获取应用的配置 关键字:env

3)cluster (集群) 一个应用下不同实例的分组,比如典型的可以按照数据中心分,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。 关键字:cluster

4)namespace (命名空间) 一个应用下不同配置的分组,可以简单地把namespace类比为文件,不同类型的配置存放在不同的文件中, 如数据库配置文件,RPC配置文件,应用自身的配置文件等 关键字:namespaces

在这里插入图片描述

3.4 配置发布原理

在这里插入图片描述

3.4.1发送ReleaseMessage

Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。 从概念上来看,这是一个典型的消息使用场景,Admin Service作为producer(生产者)发出消息,各个Config Service作为consumer(消费者)消费消息。通过一个消息队列组件(Message Queue)就能很好的实现Admin Service和Config Service的解耦。 在实现上,考虑到Apollo的实际使用场景,以及为了尽可能减少外部依赖,我们没有采用外部的消息中间件,而是 通过数据库实现了一个简单的消息队列。

具体实现方式如下:

1).Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的 AppId+Cluster+Namespace
SELECT * FROM ApolloConfigDB.ReleaseMessage

2).Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录

3).Config Service如果发现有新的消息记录,那么就会通知到所有的消息监听器

4).NotificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端

3.4.2 Config Service通知客户端

实现方式如下:

1).客户端会发起一个Http请求到Config Service的 notifications/v2 接口NotificationControllerV2

2).NotificationControllerV2不会立即返回结果,而是把请求挂起。考虑到会有数万客户端向服务端发起长连, 因此在服务端使用了async servlet(Spring DeferredResult)来服务Http Long Polling请求。

3).如果在60秒内没有该客户端关心的配置发布,那么会返回Http状态码304给客户端。

4).如果有该客户端关心的配置发布,NotificationControllerV2会调用DeferredResult的setResult方法,传入有 配置变化的namespace信息,同时该请求会立即返回。客户端从返回的结果中获取到配置变化的namespace 后,会立即请求Config Service获取该namespace的最新配置。

3.4.3 客户端读取设计

除了之前介绍的客户端和服务端保持一个长连接,从而能第一时间获得配置更新的推送外,客户端还会定时从 Apollo配置中心服务端拉取应用的最新配置。 这是一个备用机制,为了防止推送机制失效导致配置不更新

客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval 来覆盖,单位为分钟

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/478475
推荐阅读
相关标签
  

闽ICP备14008679号