apollo配置中心之-如何启动源码及Config Service 通知配置变化源码解析

参考:appollo官方

文章目录

如何启动源码

下载源码

1、源码结构

在这里插入图片描述

2、初始化库

在这里插入图片描述

3、启动apollo-assembly

application启动配置如下:
在这里插入图片描述

VM options:

-Denv=dev
-Dspring.profiles.active=github
-Deureka.service.url=http://localhost:8080/eureka/
-Dspring.datasource.url=jdbc:mysql://localhost:3309/ApolloConfigDB?characterEncoding=utf8
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/logs/apollo-assembly.log

Program arguments:

–configservice --adminservice

这些配置其实可以参考官方的项目apollo-build-scripts项目

这个项目有一个deom.sh脚本如下:不需要完全看懂所有意思,只需看重点如下:

# meta server url定义的一些变量
config_server_url=http://localhost:8080
admin_server_url=http://localhost:8090
eureka_service_url=$config_server_url/eureka/
portal_url=http://localhost:8070

# JAVA OPTS BASE_JAVA_OPTS:基础变量
BASE_JAVA_OPTS="-Denv=dev"
CLIENT_JAVA_OPTS="$BASE_JAVA_OPTS -Dapollo.meta=$config_server_url"
#config service和admin service所需的VM options配置参数
SERVER_JAVA_OPTS="$BASE_JAVA_OPTS -Dspring.profiles.active=github -Deureka.service.url=$eureka_service_url"
#portal service所需的VM options配置参数
PORTAL_JAVA_OPTS="$BASE_JAVA_OPTS -Ddev_meta=$config_server_url -Dspring.profiles.active=github,auth -Deureka.client.enabled=false -Dhibernate.query.plan_cache_max_size=192"
#启动config service和admin service时所带的Program arguments参数
$SERVICE_JAR start --configservice --adminservice

启动报错

注意apollo-assembly这个项目是将config 和admin一起启动的, 并且是先启动config 再启动admin。因为config这个服务中整合了eureka 服务端,所以要先把eureka服务启动,然后config 和admin再注册到eureka。

注:在启动apollo-configservice的过程中会在日志中输出eureka注册失败的信息,如com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused。需要注意的是,这个是预期的情况,因为apollo-configservice需要向Meta Server(它自己)注册服务,但是因为在启动过程中,自己还没起来,所以会报这个错。后面会进行重试的动作,所以等自己服务起来后就会注册正常了。

4、启动apollo-portal

application启动配置如下:
在这里插入图片描述

VM options:

-Denv=dev
-Ddev_meta=http://localhost:8080
-Dspring.profiles.active=github,auth
-Deureka.client.enabled=false
-Dhibernate.query.plan_cache_max_size=192
-Dserver.port=8170
-Dspring.datasource.url=jdbc:mysql://localhost:3309/ApolloPortalDB?characterEncoding=utf8
-Dspring.datasource.username=root
-Dspring.datasource.password=123456
-Dlogging.file=D:/logsh/apollo-portal.log

Program arguments:

–portal

这些配置也同样参考官方的项目apollo-build-scripts项目的deom.sh脚本如下:不需要完全看懂所有意思,只需看重点如下:

 #portal service所需的VM options配置参数
PORTAL_JAVA_OPTS="$BASE_JAVA_OPTS -Ddev_meta=$config_server_url -Dspring.profiles.active=github,auth -Deureka.client.enabled=false -Dhibernate.query.plan_cache_max_size=192"

echo "==== starting portal ===="
echo "Portal logging file is $PORTAL_LOG"
# 导入VM options配置参数
export JAVA_OPTS="$PORTAL_JAVA_OPTS -Dlogging.file=./apollo-portal.log -Dserver.port=8070 -Dspring.datasource.url=$apollo_portal_db_url -Dspring.datasource.username=$apollo_portal_db_username -Dspring.datasource.password=$apollo_portal_db_password"
 # 启动portal service 的参数
$PORTAL_JAR start --portal

一、apollo整体架构

在这里插入图片描述
apollo服务端大概有3个服务:

  • Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
  • Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
  • 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进程中

二、portal service发布配置后,如何通知client

1、配置发布后的实时推送过程

步骤一、用户在Portal操作配置发布

步骤二、Portal调用Admin Service的接口操作发布

步骤三、Admin Service发布配置后,发送ReleaseMessage给各个Config Service

Admin Service发送ReleaseMessage方式:
1、 Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的AppId+Cluster+Namespace,参见DatabaseMessageSender
2、Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录,参见ReleaseMessageScanner
3、Config Service如果发现有新的消息记录,那么就会通知到所有的消息监听器(ReleaseMessageListener),如NotificationControllerV2,消息监听器的注册过程参见ConfigServiceAutoConfiguration
4、NotificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端

步骤四、Config Service收到ReleaseMessage后,通知对应的客户端

Config Service通知客户端的实现方式:

1、客户端会发起一个Http请求到Config Service的notifications/v2接口,也就是NotificationControllerV2,参见RemoteConfigLongPollService

2、NotificationControllerV2不会立即返回结果,而是通过Spring DeferredResult把请求挂起

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

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

三、客户端监听配置原理

1、客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)

2、客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。

  • 这是一个fallback机制,为了防止推送机制失效导致配置不更新
  • 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
  • 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。

3、客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中

4、客户端会把从服务端获取到的配置在本地文件系统缓存一份

  • 在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置

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

四、整个流程

在这里插入图片描述

1、通知客户端配置有更新:是通过 deferredResults 这个对象实现的。

deferredResults 对象被定成NotificationControllerV2类的成员变量。
客户端会发起一个长连接到config service服务,url对应了NotificationControllerV2.#pollNotification 方法,并返回DeferredResult对象, 返回这个对象 ,那么请求并不会立即返回,直到DeferredResult.setResult 被设置值 ,请求才会返回。
那么这个对象DeferredResult.setResult 何时调用?

  • 当配置发布—》
  • 插入消息表----》
  • config service 定时描消息表-----》
  • 有新的发布消息-----》
  • 调用监听方法NotificationControllerV2.#handleMessage-----》
  • 设置DeferredResult.setResult ------->
  • 客户端调用的NotificationControllerV2.#pollNotification方法------》
  • 感受到DeferredResult.setResult 设置,立即返回,否则60s没有配置更新 ,那么超时返回304

2、核心方法如下:

@RestController
@RequestMapping("/notifications/v2")
public class NotificationControllerV2 implements ReleaseMessageListener {
    //定义deferredResults 成员变量,设置成同步集合,防止并发
    //DeferredResultWrapper类型是DeferredResult的包装类,里面持有DeferredResult类型的变量
    private final Multimap<String, DeferredResultWrapper> deferredResults =
      Multimaps.synchronizedSetMultimap(TreeMultimap.create(String.CASE_INSENSITIVE_ORDER, Ordering.natural()));
}
//客户端定时获取 最新配置url方法
@GetMapping
  public DeferredResult<ResponseEntity<List<ApolloConfigNotification>>> pollNotification(
      @RequestParam(value = "appId") String appId,
      @RequestParam(value = "cluster") String cluster,
      @RequestParam(value = "notifications") String notificationsAsString,
      @RequestParam(value = "dataCenter", required = false) String dataCenter,
      @RequestParam(value = "ip", required = false) String clientIp) {
    // 解析 notificationsAsString 参数,创建 ApolloConfigNotification 数组。
    List<ApolloConfigNotification> notifications = null;
    //创建deferredResultWrapper 对象
    DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper(bizConfig.longPollingTimeoutInMilli());  
    deferredResultWrapper.onCompletion(() -> {
      // 当这个对象resultWrapper完成setReuslt设置, 注销已经返回的监听
      for (String key : watchedKeys) {
        deferredResults.remove(key, deferredResultWrapper);
      }
    });
    //注册DeferredResultWrapper 到 `deferredResults` 中,等待配置发生变化后通知。详见 `#handleMessage(...)` 方法。
    for (String key : watchedKeys) {
      this.deferredResults.put(key, deferredResultWrapper);
    }
    //返回DeferredResult对象,直到#handleMessage监听方法中设置了值返回
    //或等待超时返回304
    return deferredResultWrapper.getResult();
  }
//配置发布后的通知方法
@Override
  public void handleMessage(ReleaseMessage message, String channel) {
    logger.info("message received - channel: {}, message: {}", channel, message);
    List<DeferredResultWrapper> results = Lists.newArrayList(deferredResults.get(content));
    if (results.size() > bizConfig.releaseMessageNotificationBatch()) {
      largeNotificationBatchExecutorService.submit(() -> {
        logger.debug("Async notify {} clients for key {} with batch {}", results.size(), content,
            bizConfig.releaseMessageNotificationBatch());
        for (int i = 0; i < results.size(); i++) {
           //如果有配置更新,那么调用了DeferredResultWrapper类对象的setResult方法,
           //这个方法就是获取内部的成量DeferredResult 并设置值。
          results.get(i).setResult(configNotification);
        }
      });
      return;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章