apollo

 架构图:

 

 

 上图有四个核心模块:

ConfigService  服务对象是 Apollo 客户端。

提供配置获取接口
提供配置推送接口
服务于Apollo客户端

AdminService  服务对象是Apollo Portal(管理界面)。

Config Service 和admin service 读取的是相同的ApolloPortalDB数据库。因为Admin service因为需要不断开发API给

Portal ,所以回不定期重启。

提供配置管理接口
提供配置修改发布接口
服务于管理界面Portal

Client

为应用获取配置,支持实时更新
通过MetaServer获取ConfigService的服务列表
使用客户端软负载SLB方式调用ConfigService

Portal  服务对象是开发者和 开放平台 API。

Admin Service 和portal分离的原因:不同的环境中,回部署在不同的网络环境下进行隔离,

每套环境都部署一套Admin Service +Config Service ,而portal是管理界面,通过调用不同环境部署的Admin Service提供

的API接口,进行不同环境的配置管理

配置管理界面
通过MetaServer获取AdminService的服务列表
使用客户端软负载SLB方式调用AdminService

辅助服务发现模块

Eureka

用于服务发现和注册
Config/AdminService注册实例并定期报心跳
和ConfigService住在一起部署

MetaServer

Portal通过域名访问MetaServer获取AdminService的地址列表
Client通过域名访问MetaServer获取ConfigService的地址列表
相当于一个Eureka Proxy
逻辑角色,和ConfigService住在一起部署

NginxLB

和域名系统配合,协助Portal访问MetaServer获取AdminService地址列表
和域名系统配合,协助Client访问MetaServer获取ConfigService地址列表
和域名系统配合,协助用户访问Portal进行配置管理

 

实现原理:

1客户端和服务端保持一个长连接,从而能够第一时间获得配置更新的推送(通过HTTP LONG POlling实现)

2客户端还会定时从Apollo配置中心服务端拉取pull,最新配置

  @这是一个fallback机制,为了防止推送机制失效导致【配置不跟新

  @客户端定时拉去回上报本地版本,服务端一般都会返回304-Not Modified

  @定时频率为5分钟拉一次,也可以指定,apollo.refreshInterval设置

3客户端拉去到最新配置后,会保存到内存当中

4获取到的配置在本地文件系统缓存一份。。遇到服务不可用或者网络不通的时候,依然能够从本地恢复配置

5应用程序可以从apollo客户端获取最新的配置,订阅配置更新通知

 

最简的架构图:

 

 

ConfigService是一个独立的微服务,服务于Client进行配置获取。
Client和ConfigService保持长连接,通过一种拖拉结合(push & pull)的模式,实现配置实时更新的同时,保证配置更新不丢失。
AdminService是一个独立的微服务,服务于Portal进行配置管理。Portal通过调用AdminService进行配置管理和发布。
ConfigService和AdminService共享ConfigDB,ConfigDB中存放项目在某个环境的配置信息。ConfigService/AdminService/ConfigDB三者在每个环境(DEV/FAT/UAT/PRO)中都要部署一份。
Protal有一个独立的PortalDB,存放用户权限、项目和配置的元数据信息。Protal只需部署一份,它可以管理多套环境。

 

 

为了保证高可用,ConfigService和AdminService都是无状态以集群方式部署的,这个时候就存在一个服务发现问题:

引入Eureka

 

 Config/AdminService启动后都会注册到Eureka服务注册中心,并定期发送保活心跳。

 

最终的架构:

 

 

如果Apollo只支持Java客户端接入,不支持其它语言客户端接入的话,那么Client和Portal只需要引入Eureka的Java客户
端,就可以实现服务发现功能。发现目标服务后,通过客户端软负载(SLB,例如Ribbon)就可以路由到目标服务实例。 客户应用是非Java的。但是Eureka(包括Ribbon软负载)原生仅支持Java客户端,如果要为多语言开发Eureka
/Ribbon客户
端,这个工作量很大也不可控。为此,Apollo的作者引入了MetaServer这个角色,它其实是一个Eureka的Proxy,将
Eureka的服务发现接口以更简单明确的HTTP接口的形式暴露出来,方便Client/Protal通过简单的HTTPClient就可以查询
到Config/AdminService的地址列表。获取到服务实例地址列表之后,再以简单的客户端软负载(Client SLB)策略路由
定位到目标实例,并发起调用。 MetaServer本身也是无状态以集群方式部署的,那么Client
/Protal该如何发现MetaServer呢?一种传统的做法是借助
硬件或者软件负载均衡器,例如在携程采用的是扩展后的NginxLB(也称Software Load Balancer),由运维为
MetaServer集群配置一个域名,指向NginxLB集群,NginxLB再对MetaServer进行负载均衡和流量转发。
Client/Portal通过域名+NginxLB间接访问MetaServer集群。 Portal也是无状态以集群方式部署的,用户通过域名+NginxLB间接访问MetaServer集群

 

配置实时推送的原理:

流程:
    1在portal操作配置发布
    2portal调用Admin Service 的接口操作发布
    3Admin Service发布配置之后,发送ReleaseMessage给各个ConfigService
    4ConfigService 收到ReleaseMessage之后,通知对应的客户端

如何发送ReleaseMessage??
    这里没有使用外来消息中间件去实现消息的发布和消费,利用数据库表变更
    1直接网ReleaseMessage表里面插入一条记录,内容就是,Appid+Cluster+Namespace
    例如:
3    apolloTestID+default+application    2020-08-06 10:24:41
6    apolloTestID+default+publiceTest    2020-08-06 10:28:45
9    apolloTestID+default+quanapp    2020-08-06 10:38:15
10    apolloTestID+default+ymlname.yml    2020-08-06 10:38:23
21    springbootTest+default+publiceTest    2020-08-06 13:40:45
    2ConfigService会用一个线程每秒扫描一次ReleaseMessage表,判断是否有消息更新
    3如果有,通知所有的消息监听器 ReleaseMessageListener
    4得到配置发布的appid+cluster+Namespace后,通知对应的客户端

ConfigService通知客户端的实现方式
    1客户端会发起一个Http请求到Config Service的notifications/v2接口
    2接口不会立即返回结果,而是使用spring DeferredResult把请求挂起来
    3如果60s没有客户端关心的配置发布,返回状态码304
    4如果有,接口会调用DeferredResult的setresult的方法,传入变化的namespace信息,请求会立即返回。
    4得到变化的namespace信息之后,立即请求ConfigService获取最新陪你

spring集成apollo的原理:
    PropertySource 属性源,就是多个key-value的属性的配置
    注意:PropertySource之间是有优先级顺序的,同一个key在不同的源里面,前面的PropertySource是优先的



apollo服务端可以通过/prometheus暴露prometheus格式的metrics的。
http://localhost:8080/prometheus
http://localhost:8070/prometheus
http://localhost:8090/prometheus



注意:!!!
如果使用EnableAolloConfig注解去开启apollo。Apollo一定会去取默认的配置文件!!!application.properties
如果使用配置文件中的apollo.bootstrap.namespaces: application.xml就不会去获取application.propertie

 

 

 

 

 

官网quickStart,

 

 

spring-boot-User-Apollo

apollo的web地址:http://localhost:8070/

apollo的Eurka地址:http://localhost:8080/

 

默认的配置:

adminService(8090) 服务对象是portal
configService(8080). 服务对象是客户端,客户端配置的拉去和推送 
portalService(8070) 管理web界面,多套adminservice对应一个portalservice

 

先在apollo里面配置一个项目:

 

 环境为:DEV

Appid:springbootTest

两个值:server.port = 8686  timeout = 23112

 

spring-boot项目:

添加apollo客户端的依赖:

        <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.1.0</version>
        </dependency>

 

配置文件:

 

apollo.boostrap.enabled = true    在应用的启动阶段,向spring容器注入被托管的application.properties
app.id = springbootTest    APPID 是配置中心获取配置的一个重要信息
apollo.bootstrap.eagerLoad.enabled=true    将Apollo配置家在提到初始化日志系统之前,要不然可能没有关于apollo的日志输出
apollo.meta = http://127.0.0.1:8080  apolloService的地址
server.port= 8989
timeout=
logging.level.com.quan.apollo.controller = debug    选

注意:http://127.0.0.1:8080这个地址并不是我们访问的apollo WEB端的地址,是自动发现的端口,因为我们要

通过自动发现去拿到我们需要的portal service和configService

@RestController
public class Aptest {
    @Value("${server.port}")
    String port;

    @Value("${timeout}")
    String time;


    @RequestMapping("done")
    public void apTest(){
        System.out.println(port);
        System.out.println(time);
    }
}

 

java-User-apolloAPI-Test:

普通的java项目,通过加入系统参数进行配置apollo:

-Dapp.id=apolloTestID 
-Dapollo.meta=http://localhost:8080 
-Dapollo.configService=http://localhost:8080
-Denv=DEV

添加依赖:

    <dependency>
      <groupId>com.ctrip.framework.apollo</groupId>
      <artifactId>apollo-client</artifactId>
      <version>1.1.0</version>
    </dependency>

如果运行时报错:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defa

再添加一个依赖:

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-nop</artifactId>
      <version>1.7.2</version>
    </dependency>

使用apolloAPI:

public class ApolloT {
    public static void main(String[] args) {
        Config config = ConfigService.getConfig("application");
        System.out.println(config);
        String somekey = "timeout";
        String va = config.getProperty(somekey,"de");
        System.out.println(va);
    }
}

 

 

 re:

 

监听配置变化事件: 

 public static void main(String[] args) {
        while (true){
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Config config = ConfigService.getConfig("application");
            config.addChangeListener(new ConfigChangeListener() {
                @Override
                public void onChange(ConfigChangeEvent configChangeEvent) {
                    System.out.println("Changes for namespace" + configChangeEvent.getNamespace());
                    for (String key :configChangeEvent.changedKeys()){
                        ConfigChange change = configChangeEvent.getChange(key);
                        System.out.println(change.getOldValue()+"###"+change.getNewValue()+"###"+change.getChangeType());
                    }
                }
            });
            System.out.println(config);
            String somekey = "timeout";
            String va = config.getProperty(somekey,"de");
            System.out.println(va);

        }

    }
}

 

运行之后,修改apollo上的配置:

com.ctrip.framework.apollo.internals.DefaultConfig@358c99f5
98984444
Changes for namespaceapplication
98984444###98984444000###MODIFIED
com.ctrip.framework.apollo.internals.DefaultConfig@358c99f5
98984444000

 

namespace;

 

 

java API 访问:

public class ApolloT {
    public static void main(String[] args) {
        while (true){
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Config config1 = ConfigService.getConfig("application");
            config1.addChangeListener(new ConfigChangeListener() {
                @Override
                public void onChange(ConfigChangeEvent configChangeEvent) {
                    System.out.println("Changes for namespace" + configChangeEvent.getNamespace());
                    for (String key :configChangeEvent.changedKeys()){
                        ConfigChange change = configChangeEvent.getChange(key);
                        System.out.println(change.getOldValue()+"###"+change.getNewValue()+"###"+change.getChangeType());
                    }
                }
            });
            System.out.println(config1);
            String somekey = "timeout";
            String va = config1.getProperty(somekey,"de");
            System.out.println(va);

            Config config2 = ConfigService.getConfig("publiceTest");
            System.out.println(config2.getProperty("name","Q"));
        }

    }
}

 


com.ctrip.framework.apollo.internals.DefaultConfig@358c99f5
98984444000
quanQQ
com.ctrip.framework.apollo.internals.DefaultConfig@358c99f5
98984444000
quanQQ

springboot注解apollo 

家依赖

     <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.1.0</version>
        </dependency>

 

获取配置的类

@RestController
public class DController {

    private static Logger logger = LoggerFactory.getLogger(DController.class);
    @Value("${server.port}")
    String port;

    //公共namespaces
    @Value("${name}")
    String name;

    @Value("${timeout:2323}") //取不到可以按照冒号后面去取值
    String timeout;

    @Value("${myname:initmyname}")
    String myname;


    @RequestMapping("/done")
    public void done(){
        System.out.println(port+"  "+name+"   "+timeout +"  "+myname);
    }

}

 

主程序加入注解:

//@EnableApolloConfig这个注解不需要注解到启动类上,springboot管理的类就行
@EnableApolloConfig(value = {"application","ymlname.yml","publiceTest"})
//@EnableApolloConfig("application.yml")
@SpringBootApplication
public class DoneApplication {

    public static void main(String[] args) {
        SpringApplication.run(DoneApplication.class, args);
    }
}

 

如果需要实时监听:

@Configuration
public class LoggerConfig {

    private static final Logger logger = LoggerFactory.getLogger(LoggerConfig.class);
    private static final String LOGGER_TAG = "logging.level.com"; //设定key

    @Autowired
    private LoggingSystem loggingSystem;

    @ApolloConfig
    private Config config;// 将服务端中的配置注入到这个类

    @ApolloConfigChangeListener  //监听配置中心监听事件,发生则调用refreshLoggingLevels,默认监听application
    private void configChangeListter(ConfigChangeEvent changeEvent) {
        Set<String> keyNames = changeEvent.changedKeys();//先获取变化的所有key
        for (String key : keyNames) {  //遍历
            System.out.println("更新配置的key包括:"+keyNames);
//            判断是不是日志的value更新,是的话就设置日志级别,并输出提示
            if (StringUtils.equalsIgnoreCase(key, LOGGER_TAG)) {  //如果key和原来设置的key一样,就是日志
                String strLevel = config.getProperty(key, "info");  //获取key对应的value
                LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());  //将value设置为大写
                loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level);//设置logger的级别
                logger.info("{}:{}", key, strLevel);//输出
            }
        }
    }

}

 

 

 

 

 

 

namespace的格式:
配置文件有多种格式,例如:properties、xml、yml、yaml、json等
在Portal UI中可以看到“application”的Namespace上有一个“properties”标签,表明“application”是properties格式的

namespace的类型;

私有类型,共有类型,关联类型

 

公共类型,publice,相当于不受控制,不接受应该的约束。且通过namspace去

唯一表示公共的namespace ,所以!!!!公共namespaces必须全局唯一

如果不唯一,很有可能直接得到的配置是私有类型或者关联类型的配置。

使用场景:部门级别共享的配置,小组的配置

私有类型;只能当前应用拉去。

 

关联类型,就是建立一个私有类型的namespace去继承共有类型,可以对共有类型的所有属性进行覆盖修改。

提供一份公司默认的配置且可调整

同时每个用户也是可以自定义修改的

简单来讲,就是,一个是default,一个是owner,其中owner没有进行设置的就使用default

 

 

apollo的公共开发接口:

获取某个Namespace信息接口.
URL : http://localhost:8070/openapi/v1/envs/DEV/apps/springbootTest/clusters/default/namespaces/application
Method : GET
Request Params :无

 

apollo的坑:

Apollo出现系统出错,请联系。。。。。。

这里一般都是数据库错误,链接不上或者其他
这里的出错原因是数据库ApolloConfigDB里面的表ServerConfig里面的key eureka.service.url 里面的
端口是8080,因为我的端口8080被占用了,所以,通过修改端口可以解决问题

 

Spring-boot在使用apollo的时候,配置是否会进行自动刷新:
使用@Value 🎍的属性是可以的
但是@ConfigurataionProperties 就进行特殊的处理

 

posted @ 2020-08-07 07:45  小丑quan  阅读(624)  评论(0)    收藏  举报