介绍当新节点加入集群或集群内节点下线时,可以发现集群间的健康检查。健康检查的频率是多少?节点状态如何变化?状态变化会触发什么动作。带着这些问题,本文作一简要介绍。一、内容概要内容概要健康检查Nacos节点会向集群中的其他节点发送健康检查心跳。每轮的频率为2秒。当健康检查异常时,将其设置为不受信任的“SUSPICIOUS”状态。如果最大失败次数超过3次,则设置为DownLine“DOWN”状态健康检查成功将节点设置为Kecom的“UP”状态,无论成功还是失败当节点状态改变时,MembersChangeEvent事件为发出MembersChangeEvent当集群节点成员发生变化时,MemberChangeListener会收到该事件,例如回调ClusterRpcClientProxy#onEvent触发refresh刷新本节点和集群其他节点的RPC状态,关闭无效或新增RPC连接2.健康检查代码转为ServerMemberManager#onApplicationEvent,Nacos启动时会启动一个定时任务,第一次延时5秒执行。这个定时任务负责节点间的心跳。@OverridepublicvoidonApplicationEvent(WebServerInitializedEventevent){getSelf().setState(NodeState.UP);if(!EnvUtil.getStandaloneMode()){//注解@1GlobalExecutor.scheduleByCommon(this.infoReportTask,5_000L);}EnvUtil.setPort(event.getWebSer().getPort());EnvUtil.setLocalAddress(this.localAddress);Loggers.CLUSTER.info("Thisnodeisreadytoprovideexternalservices");}注意@1非单机模式延迟执行5秒,执行的infoReportTask为MemberInfoReportTask。publicabstractclassTaskimplementsRunnable{protectedvolatilebooleanshutdown=false;@Overridepublicvoidrun(){//注解@2if(shutdown){return;}try{executeBody();}catch(Throwablet){Loggers.CORE.error("thistaskexecutehaserror:{}",ExceptionUtil.getStackTrace(t));}finally{if(!shutdown){after();}}}}注@2看这个Task的执行逻辑,先执行executeBody(),执行完再执行after()。classMemberInfoReportTaskextendsTask{privatefinalGenericType>reference=newGenericType>(){};privateintcursor=0;@OverrideprotectedvoidexecuteBody(){//--------注@1start-------------//获取集群中除自身以外的其他节点列表Listmembers=ServerMemberManager.this.allMembersWithoutSelf();if(members.isEmpty()){return;}//定义一个cursorthis.cursor=(this.cursor+1)%members.size();//获取每个section的信息Membertarget=members.get(cursor);//-----------注解@1end----------------Loggers.CLUSTER.debug("reportthemetadatatothenode:{}",target.getAddress());//注解@2finalStringurl=HttpUtils.buildUrl(false,target.getAddress(),EnvUtil.getContextPath(),Commons.NACOS_CORE_CONTEXT,"/cluster/report");try{//注解@3asyncRestTemplate.post(url,Header.newInstance().addParam(Constants.NACOS_SERVER_HEADER,VersionUtils.version),Query.EMPTY,getSelf(),reference.getType(),newCallback(){@OverridepublicvoidonReceive(RestResult<String>result){//注解@4//注解@5返回不一致的版本if(result.getCode()==HttpStatus.NOT_IMPLEMENTED.value()||result.getCode()==HttpStatus.NOT_FOUND.value()){//...MembermemberNew=target.copy();if(memberNew.getAbilities()!=null&&memberNew.getAbilities().getRemoteAbility()!=null&&memberNew.getAbilities().getRemoteAbility().isSupportRemoteConnection()){memberNew.getAbilities().getRemoteAbility().setSupportRemoteConnection(false);update(memberNew);//更新节点属性}return;}//注解@6if(result.ok()){MemberUtil.onSuccess(ServerMemberManager.this,target);}else{//注解@7处理失败上报MemberUtil.onFail(ServerMemberManager.this,target);}}@OverridepublicvoidonError(Throwablethrowable){//注解@8处理失败上报MemberUtil.onFail(ServerMemberManager.this,target,throwable);}@OverridepublicvoidonCancel(){}});}catch(Throwableex){//...}}@Overrideprotectedvoidafter(){GlobalExecutor.scheduleByCommon(this,2_000L);//注意@9}}注@1获取集群中除自身以外的其他节点列表,通过游标循环遍历每个节点Note@2构造每个节点的reportingurl请求路径为“/cluster/report”Note@3发起Post健康检查请求,请求内容为自己的成员信息Note@4处理健康检查返回结果,有以下三种注@5版本过低的错误,这可能是集群中版本不一致造成的注@6处理成功上报,更新节点成员状态为UP表示分支通信,设置数量失败为0,发布成员变更事件/状态为UP,可以通信member.setFailAccessCnt(0);//失败次数为0if(!Objects.equals(old,member.getState())){manager.notifyMemberChange();//发布成员变更event}}Annotation@7&Annotation@8是processi的报告ng失败,例如:cluster其中一个节点被kill-9杀死后。nacos-cluster.log日志文件中会打印如下日志,成员变更事件2021-07-0x16:30:24,994ERRORfailedtoreportnewinfototargetnode:x.x.x.x:8848,error:caused:Connectionrefused;:2021-07-0x16:30:30,995会发出ERRORfailedtoreportnewinfototargetnode:x.x.x.x:8848,error:caused:Connectionrefused;publicstaticvoidonFail(finalServerMemberManagermanager,finalMembermember,Throwableex){manager.getMemberAddressInfos().remove(member.getAddress());final/NodeStateold(setthenodeasmemb)"Don'ttrust"member.setState(NodeState.SUSPICIOUS);//失败次数增加+1member.setFailAccessCnt(member.getFailAccessCnt()+1);//默认最大失败重试次数为3intmaxFailAccessCnt=EnvUtil.getProperty("nacos.core.member.fail-access-cnt",Integer.class,3);//如果访问目标节点的连续失败次数达到//最大,或者链接请求被拒绝,则状态直接down//超过重试次数,设置节点状态为"离线”如果(成员r.getFailAccessCnt()>maxFailAccessCnt||StringUtils.containsIgnoreCase(ex.getMessage(),TARGET_MEMBER_CONNECT_REFUSE_ERRMSG)){member.setState(节点State.DOWN);}if(!Objects.equals(old,member.getState())){manager.notifyMemberChange();//释放成员变更事件}}kill-9杀死的节点显示状态为离线DOWN注@9executeBody执行后延时2秒继续执行executeBody,即健康检查的心跳频率为2秒。一轮所有节点检查完成后,延时2秒再进行下一轮,无论检查成功还是失败,当节点状态发生变化时,发布成员变化事件if(!Objects.equals(old,member.getState())){manager.notifyMemberChange();}voidnotifyMemberChange(){NotifyCenter.publishEvent(MembersChangeEvent.builder().members(allMembers())).build());}总结:Nacos节点会发送健康检查向集群中的其他节点发送心跳,每轮频率为2秒;当健康检查异常时,设置为不可信“SUSPICIOUS”状态,超过最大失败次数3次设置为离线“DOWN”状态;健康检查成功将节点设置为Kecom的“UP”状态;无论成功还是失败,当节点状态改变时,都会发出MembersChangeEvent事件。3.成员变更事件当集群中某个节点下线或有新节点上线时,通过心跳健康检查检测,该节点的状态会发生变化。状态的改变会触发MembersChangeEvent。那么为什么要订阅这个事件呢?ClusterRpcClientProxy继承了MemberChangeListener,当有MembersChangeEvent事件时,会回调它的onEvent方法。@OverridepublicvoidonEvent(MembersChangeEventevent){try{Listmembers=serverMemberManager.allMembersWithoutSelf();refresh(members);}catch(NacosExceptione){//...}}再看刷新方法。privatevoidrefresh(Listmembers)throwsNacosException{for(Membermember:members){if(MemberUtil.isSupportedLongCon(member)){//注意@10createRpcClientAndStart(member,ConnectionType.GRPC);}}Set>allClientEntrys=RpcClientFactory.getAllClientEntries();Iterator>iterator=allClientEntrys.iterator();ListnewMemberKeys=members.stream().filter(a->MemberUtil.isSupportedLongCon(a)).map(a->memberClientKey(a)).collect(Collectors.toList());//关闭旧的grpc连接while(iterator.hasNext()){Map.Entrynext1=iterator.next();if(next1.getKey().startsWith("Cluster-")&&!newMemberKeys.contains(next1.getKey())){Loggers.CLUSTER.info("memberleave,destroyclientofmember->:{}",next1.getKey());RpcClientFactory.getClient(next1.getKey()).shutdown();iterator.remove();}}}注意@10为集群中的每个节点成员创建rcp客户端,在client启动时,目标节点会先发送HealthCheckReqUest,如果不健康的节点会被移除,请参考RpcClient类的代码。booleanisHealthy=healthCheck();//节点不健康currentConnection.getConnectionId());//标记客户端状态为unhealthyrpcClientStatus.set(RpcClientStatus.UNHEALTHY);//重置ReconnectContext并移除serverInforeconnectContext=newReconnectContext(null,false);离线节点的rpc会失效;同样,如果一个新节点加入集群,一个新的rpc通道将被建立。总结:当集群节点成员发生变化时,MemberChangeListener会收到这个事件。比如回调ClusterRpcClientProxy#onEvent触发刷新。刷新本节点与集群中其他节点的RPC状态,关闭无效或增加新的RPC连接。本文转载自微信公众号“瓜农老粮”,可通过以下二维码关注。转载本文请联系瓜农老梁公众号。