Nacos 实例注册与同步解析

最近打算将部分应用修改为SpringCloud的形式,减少内部各种奇葩域名的出现,在考察 注册中心 时将该代码大致了解了一下,记录一下.

针对临时实例,Nacos版本:1.1.3 最新master分支. 克隆到本地,将 naming pom.xml文件移除 maven-assembly-plugin, 包修改为SpringBoot配置

  1. <plugin>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-maven-plugin</artifactId>
  4. <version>2.1.1.RELEASE</version>
  5. <configuration>
  6. <mainClass>com.alibaba.nacos.naming.NamingApp</mainClass>
  7. <layout>ZIP</layout>
  8. </configuration>
  9. <executions>
  10. <execution>
  11. <goals>
  12. <goal>repackage</goal>
  13. </goals>
  14. </execution>
  15. </executions>
  16. </plugin>

分别拷贝2份 path/naming/target/nacos-naming-1.1.3.jar到 /Users/admin/Desktop/note/Users/admin/Desktop/task ,分别配置3份 nacos/conf/cluster.conf,内容如下

  1. 192.168.63.127:18848
  2. 192.168.63.127:8848
  3. 192.168.63.127:8858

最后分别启动这3个实例. 2个jar包启动,1个idea启动

注册流程

执行 curl 注册

  1. curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?port=9112&healthy=true&ip=11.11.11.11&weight=1.0&serviceName=nacos.test.3'

针对 InstanceController 发起 POST 请求

  • 服务不存在则创建空的服务 createEmptyService, 原因:Nacos是基于 service <---> cluster <---> instance 作为模型,所有实例的存在都必须依托于 service , 涉及服务的初始化和监听器的启动
    • service 初始化,启动 ClientBeatCheckTask 5s定时任务,用于标记实例不健康以及删除实例,服务集群初始化.
    • 加入2个监听器,后期实例发生变化触发 onChange 操作
  • 获取添加后的全部实例,和当前存储的服务数据进行比较对实例进行 add/delete, 此处不涉及数据的更新操作.
  • 保存服务
    • DistroConsistencyServiceImpl#onPut 数据保持到本地,添加一个 change 事件到队列,由 notifier 线程异步执行监听器操作,触发 service 的checksum计算,更新实例IP信息.
    • TaskDispatcher#addTask添加一个任务到调度队列中,由 TaskScheduler 异步执行发送给其他服务节点。

同步流程

同步流程紧跟上述服务注册.

TaskScheduler#run 将队列中的 key 取出,对其他服务节点发起 PUT 请求 ,地址 [http://127.0.0.1:18848/nacos/v1/ns/distro/datum](http://127.0.0.1:18848/nacos/v1/ns/distro/datum)

  1. public static boolean syncData(byte[] data, String curServer) throws Exception {
  2. try {
  3. Map<String, String> headers = new HashMap<>(128);
  4. HttpClient.HttpResult result = HttpClient.httpPutLarge("http://" + curServer + RunningConfig.getContextPath()
  5. + UtilsAndCommons.NACOS_NAMING_CONTEXT + DATA_ON_SYNC_URL, headers, data);
  6. if (HttpURLConnection.HTTP_OK == result.code) {
  7. return true;
  8. }
  9. if (HttpURLConnection.HTTP_NOT_MODIFIED == result.code) {
  10. return true;
  11. }
  12. throw new IOException("failed to req API:" + "http://" + curServer+ RunningConfig.getContextPath()+ UtilsAndCommons.NACOS_NAMING_CONTEXT + DATA_ON_SYNC_URL + ". code:"+ result.code + " msg: " + result.content);
  13. } catch (Exception e) {
  14. Loggers.SRV_LOG.warn("NamingProxy", e);
  15. }
  16. return false;
  17. }

请求到达 DistroController#onSyncDatum, 其他服务节点收到请求后,进行上述注册流程,省去 TaskDispatcher#addTask 部分,因为该服务不属于这个节点处理,所以不需要进行同步。到此服务注册和同步流程结束

因为都是属于后台异步处理,服务节点达到一致需要一定的时间,但会保证最终的一致.

实例过期

在服务注册中通过创建空的服务初始化了服务的定时任务,该定时任务会执行实例过期和实例删除操作。

标记不健康实例

  1. for (Instance instance : instances) {
  2. if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {
  3. if (!instance.isMarked()) {
  4. if (instance.isHealthy()) {
  5. instance.setHealthy(false);//实例心跳过期,标记实例不健康,但是不删除实例
  6. getPushService().serviceChanged(service);
  7. SpringContext.getAppContext().publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));
  8. }
  9. }
  10. }
  11. }

删除监控实例

  1. for (Instance instance : instances) {
  2. if (instance.isMarked()) {
  3. continue;
  4. }
  5. if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {
  6. deleteIP(instance);//删除实例
  7. }
  8. }

执行删除实例 ,向自己发起一个 DELETE 的http请求,等同于 客户端 主动调用注销请求

  1. curl -X DELETE 127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.test.1&ip=1.1.1.1&port=8888&clusterName=TEST1

请求到达 InstanceController#deregister , 执行服务移除操作.

  • 将服务从缓存中移除,例子如下:缓冲中有数据 1,2,3,4 , 4 过期,可获取到最终数据 1,2,3.
  • 执行 注册流程 的第二步保持服务,因为注册和注销本质都是服务变更.

2019-08-24