Nacos服务注册原理分析( 二 )

registerInstance主要做两件事:

  • 发送心跳包
beatReactor.addBeatInfo使用定时服务,每隔5s向服务端发送一次心跳请求,通过http请求发送心跳信息,路径为/v1/ns/instance/beat 。
心跳请求定时任务使用线程池ScheduledThreadPoolExecutor.schedule(),而该方法只会调用一次,定时任务的实现是在每次请求任务只会再调用一次ScheduledThreadPoolExecutor.schedule(), 简单说就是nacos在发送心跳的时候,会调用schedule方法,在schedule要执行的任务中,如果正常发送完心跳,会再次调用schedule方法 。
那为什么不直接调用周期执行的线程池ScheduledThreadPoolExecutor.scheduleAtFixedRate()?可能是由于发送心跳服务发生异常后,定时任务还会继续执行,但是周期执行的线程池遇到报错后也不会重复调用执行的任务 。
线程任务BeatTask的run方法,,每次执行会先判断isStopped,如果是false,说明心跳停止,就不会触发下次执行任务 。如果使用定时任务scheduleAtFixedRate,即使心跳停止还会继续执行任务,造成资源不必要浪费 。
  • 注册实例
registerService主要封装实例信息,比如ip、port、servicename,将这些信息通过http请求发送给服务端 。路径为/v1/ns/instance 。
根据上面流程,查看以下的流程图:
Nacos服务注册原理分析

文章插图
 
服务端服务端就是注册中心,服务注册到注册中心,在https://github.com/alibaba/nacos/releases/tag/2.1.1下载源码部署到本地,方便调式和查看,部署方式详见我的另外一篇文章Nacos 源码环境搭建 。
服务端主要接收两个信息:心跳包和实例信息 。
心跳包客户端向服务请求的路径为/v1/ns/instance/beat,对应的服务端为InstanceController类的beat方法:
@PutMapping("/beat")@Secured(action = ActionTypes.WRITE)public ObjectNode beat(HttpServletRequest request) throws Exception {ObjectNode result = JacksonUtils.createEmptyJsonNode();result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval());String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY);RsInfo clientBeat = null;// 判断是否有心跳,存在心跳就转成RsInfoif (StringUtils.isNotBlank(beat)) {clientBeat = JacksonUtils.toObj(beat, RsInfo.class);}String clusterName = WebUtils.optional(request, CommonParams.CLUSTER_NAME, UtilsAndCommons.DEFAULT_CLUSTER_NAME);String ip = WebUtils.optional(request, "ip", StringUtils.EMPTY);int port = Integer.parseInt(WebUtils.optional(request, "port", "0"));if (clientBeat != null) {if (StringUtils.isNotBlank(clientBeat.getCluster())) {clusterName = clientBeat.getCluster();} else {// fix #2533clientBeat.setCluster(clusterName);}ip = clientBeat.getIp();port = clientBeat.getPort();}String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);NamingUtils.checkServiceNameFormat(serviceName);Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}, namespaceId: {}", clientBeat,serviceName, namespaceId);// 获取实例信息BeatInfoInstanceBuilder builder = BeatInfoInstanceBuilder.newBuilder();builder.setRequest(request);int resultCode = getInstanceOperator().handleBeat(namespaceId, serviceName, ip, port, clusterName, clientBeat, builder);result.put(CommonParams.CODE, resultCode);// 下次发送心跳包间隔result.put(SwitchEntry.CLIENT_BEAT_INTERVAL,getInstanceOperator().getHeartBeatInterval(namespaceId, serviceName, ip, port, clusterName));result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());return result;}在handleBeat方法中执行线程任务ClientBeatProcessorV2的run方法,延长lastHeartBeatTime时间 。注册中心会定时查询实例,当前时间 - lastHeartBeatTime > 设置时间(默认15秒),就标记实例为不健康实例 。如果心跳实例不健康,发送通知给订阅方,变更实例 。
服务端在15秒没有收到心跳包会将实例设置为不健康,在30秒没有收到心跳包会将临时实例移除掉 。
实例注册客户端请求的地址是/nacos/v1/ns/instance,对应的是服务端是在InstanceController类 。找到类上对应的post请求方法上 。
注册流程:
InstanceController#register ——>InstanceOperatorClientImpl#registerInstance ——>ClientOperationServiceProxy#registerInstance ——>EphemeralClientOperationServiceImpl#registerInstance
创建 Service服务注册后,将服务存储在一个双层map集合中:
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();


推荐阅读