【nacos】源码之服务端服务健康检查与服务查询
admin
2024-03-29 02:43:58
0

Nacos服务端在实例进行注册时,会在服务初始化的时候开启一个客户端心跳检测任务ClientBeatCheckTask。

ClientBeatCheckTask的添加

每一个Service初始化时都会添加一个与之对应的一个ClientBeatCheckTask。

com.alibaba.nacos.naming.core.Service#init

public void init() {// 开启客户端心跳检测任务HealthCheckReactor.scheduleCheck(clientBeatCheckTask);for (Map.Entry entry : clusterMap.entrySet()) {entry.getValue().setService(this);entry.getValue().init();}
}

这个任务默认5秒执行一次。
com.alibaba.nacos.naming.healthcheck.HealthCheckReactor#scheduleCheck(com.alibaba.nacos.naming.healthcheck.ClientBeatCheckTask)

public static void scheduleCheck(ClientBeatCheckTask task) {futureMap.computeIfAbsent(task.taskKey(),k -> GlobalExecutor.scheduleNamingHealth(task, 5000, 5000, TimeUnit.MILLISECONDS));
}

ClientBeatCheckTask的执行

ClientBeatCheckTask实现了Runnable接口,主要看run()方法。

遍历服务中的所有实例,如果心跳时间超过15s就将健康状态标记为false,如果心跳时间超过30s就会删除实例。

将健康状态标记为false时会发布InstanceHeartbeatTimeoutEvent和ServiceChangeEvent两个事件。

InstanceHeartbeatTimeoutEvent没有地方监听此事件。

ServiceChangeEvent由本身发布事件的PushService监听。

com.alibaba.nacos.naming.healthcheck.ClientBeatCheckTask#run

public void run() {try {if (!getDistroMapper().responsible(service.getName())) {return;}if (!getSwitchDomain().isHealthCheckEnabled()) {return;}List instances = service.allIPs(true);// first set health status of instances:for (Instance instance : instances) {// 心跳时间超过15s就将健康状态标记为falseif (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {if (!instance.isMarked()) {if (instance.isHealthy()) {instance.setHealthy(false);Loggers.EVT_LOG.info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client timeout after {}, last beat: {}",instance.getIp(), instance.getPort(), instance.getClusterName(),service.getName(), UtilsAndCommons.LOCALHOST_SITE,instance.getInstanceHeartBeatTimeOut(), instance.getLastBeat());getPushService().serviceChanged(service);ApplicationUtils.publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));}}}}if (!getGlobalConfig().isExpireInstance()) {return;}// then remove obsolete instances:for (Instance instance : instances) {if (instance.isMarked()) {continue;}// 心跳时间超过30s就会删除实例if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {// delete instanceLoggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(),JacksonUtils.toJson(instance));deleteIp(instance);}}} catch (Exception e) {Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);}}

ClientBeatCheckTask#deleteIp

调用自己的/nacos/v1/ns/instance注销实例。

com.alibaba.nacos.naming.healthcheck.ClientBeatCheckTask#deleteIp

private void deleteIp(Instance instance) {try {NamingProxy.Request request = NamingProxy.Request.newRequest();request.appendParam("ip", instance.getIp()).appendParam("port", String.valueOf(instance.getPort())).appendParam("ephemeral", "true").appendParam("clusterName", instance.getClusterName()).appendParam("serviceName", service.getName()).appendParam("namespaceId", service.getNamespaceId());// 调用自己的/nacos/v1/ns/instance注销实例String url = "http://" + IPUtil.localHostIP() + IPUtil.IP_PORT_SPLITER + EnvUtil.getPort() + EnvUtil.getContextPath()+ UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance?" + request.toUrl();// delete instance asynchronously:HttpClient.asyncHttpDelete(url, null, null, new Callback() {@Overridepublic void onReceive(RestResult result) {if (!result.ok()) {Loggers.SRV_LOG.error("[IP-DEAD] failed to delete ip automatically, ip: {}, caused {}, resp code: {}",instance.toJson(), result.getMessage(), result.getCode());}}@Overridepublic void onError(Throwable throwable) {Loggers.SRV_LOG.error("[IP-DEAD] failed to delete ip automatically, ip: {}, error: {}", instance.toJson(),throwable);}@Overridepublic void onCancel() {}});} catch (Exception e) {Loggers.SRV_LOG.error("[IP-DEAD] failed to delete ip automatically, ip: {}, error: {}", instance.toJson(), e);}
}

InstanceController#deregister

com.alibaba.nacos.naming.controllers.InstanceController#deregister

public String deregister(HttpServletRequest request) throws Exception {Instance instance = getIpAddress(request);String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);NamingUtils.checkServiceNameFormat(serviceName);// 查询出ServiceService service = serviceManager.getService(namespaceId, serviceName);if (service == null) {Loggers.SRV_LOG.warn("remove instance from non-exist service: {}", serviceName);return "ok";}// 删除实例serviceManager.removeInstance(namespaceId, serviceName, instance.isEphemeral(), instance);return "ok";
}

ServiceManager#removeInstance

这里只是将注册表的实例列表取出然后删除,并放入缓存中,并没有删除注册表中的实例。

com.alibaba.nacos.naming.core.ServiceManager#removeInstance(java.lang.String, java.lang.String, boolean, com.alibaba.nacos.naming.core.Instance…)

public void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)throws NacosException {Service service = getService(namespaceId, serviceName);synchronized (service) {removeInstance(namespaceId, serviceName, ephemeral, service, ips);}
}private void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Service service,Instance... ips) throws NacosException {String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);// 将缓存中的实例列表删除实例List instanceList = substractIpAddresses(service, ephemeral, ips);Instances instances = new Instances();instances.setInstanceList(instanceList);// 更新缓存consistencyService.put(key, instances);
}private List substractIpAddresses(Service service, boolean ephemeral, Instance... ips)throws NacosException {return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE, ephemeral, ips);
}

真正从注册表中删除实例与注册实例一样通过同一个异步任务来完成。

ServiceChangeEvent事件的监听

PushService监听了ServiceChangeEvent事件。

当服务变更时,Nacos服务端会通过UDP通知所有监听此服务的客户端。这里为了使客户端能够实时的知道服务的实例状态变更了,又为了不增加服务器的压力,所以使用了UDP,因为UDP不需要建立连接,直接发送一个报文即可,不管客户端有没有收到。即使客户端没有收到,客户端也有一个定时任务每隔5s来查询服务的实例列表。

com.alibaba.nacos.naming.push.PushService#onApplicationEvent

public void onApplicationEvent(ServiceChangeEvent event) {Service service = event.getService();String serviceName = service.getName();String namespaceId = service.getNamespaceId();Future future = GlobalExecutor.scheduleUdpSender(() -> {try {Loggers.PUSH.info(serviceName + " is changed, add it to push queue.");/*** client什么时候加入?客户端查询服务的实例列表时* @see InstanceController#doSrvIpxt(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, boolean, java.lang.String, java.lang.String, boolean)*/ConcurrentMap clients = clientMap.get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));if (MapUtils.isEmpty(clients)) {return;}Map cache = new HashMap<>(16);long lastRefTime = System.nanoTime();// 遍历所有查询过此服务的客户端列表for (PushClient client : clients.values()) {if (client.zombie()) {Loggers.PUSH.debug("client is zombie: " + client.toString());clients.remove(client.toString());Loggers.PUSH.debug("client is zombie: " + client.toString());continue;}Receiver.AckEntry ackEntry;Loggers.PUSH.debug("push serviceName: {} to client: {}", serviceName, client.toString());String key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());byte[] compressData = null;Map data = null;if (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);compressData = (byte[]) (pair.getValue0());data = (Map) pair.getValue1();Loggers.PUSH.debug("[PUSH-CACHE] cache hit: {}:{}", serviceName, client.getAddrStr());}if (compressData != null) {ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);} else {ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);if (ackEntry != null) {cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));}}Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",client.getServiceName(), client.getAddrStr(), client.getAgent(),(ackEntry == null ? null : ackEntry.key));// 将变更服务的实例列表通过UDP发送给客户端udpPush(ackEntry);}} catch (Exception e) {Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);} finally {futureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));}}, 1000, TimeUnit.MILLISECONDS);futureMap.put(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName), future);}

查询服务的实例列表

Nacos客户端会通过调用接口/nacos/v1/ns/instance/list来查询服务端对应服务的实例列表。

com.alibaba.nacos.naming.controllers.InstanceController#list

@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public ObjectNode list(HttpServletRequest request) throws Exception {String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);NamingUtils.checkServiceNameFormat(serviceName);String agent = WebUtils.getUserAgent(request);String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);int udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));String env = WebUtils.optional(request, "env", StringUtils.EMPTY);boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));String app = WebUtils.optional(request, "app", StringUtils.EMPTY);String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));// 查询serviceName对应的实例return doSrvIpxt(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant,healthyOnly);
}

com.alibaba.nacos.naming.controllers.InstanceController#doSrvIpxt

public ObjectNode doSrvIpxt(String namespaceId, String serviceName, String agent, String clusters, String clientIP,int udpPort, String env, boolean isCheck, String app, String tid, boolean healthyOnly) throws Exception {ClientInfo clientInfo = new ClientInfo(agent);ObjectNode result = JacksonUtils.createEmptyJsonNode();// 根据命名空间和服务名查询出服务Service service = serviceManager.getService(namespaceId, serviceName);long cacheMillis = switchDomain.getDefaultCacheMillis();// now try to enable the pushtry {if (udpPort > 0 && pushService.canEnablePush(agent)) {// 将监听实例变更的客户端加入到一个clientMap中,后续如果服务的实例列表状态变更了会遍历此MAP,发送UDP报文通知pushService.addClient(namespaceId, serviceName, clusters, agent, new InetSocketAddress(clientIP, udpPort),pushDataSource, tid, app);cacheMillis = switchDomain.getPushCacheMillis(serviceName);}} catch (Exception e) {Loggers.SRV_LOG.error("[NACOS-API] failed to added push client {}, {}:{}", clientInfo, clientIP, udpPort, e);cacheMillis = switchDomain.getDefaultCacheMillis();}if (service == null) {if (Loggers.SRV_LOG.isDebugEnabled()) {Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);}result.put("name", serviceName);result.put("clusters", clusters);result.put("cacheMillis", cacheMillis);result.replace("hosts", JacksonUtils.createEmptyArrayNode());return result;}checkIfDisabled(service);List srvedIPs;// 从服务service中查询出实例列表srvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));// filter ips using selector:if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) {srvedIPs = service.getSelector().select(clientIP, srvedIPs);}if (CollectionUtils.isEmpty(srvedIPs)) {if (Loggers.SRV_LOG.isDebugEnabled()) {Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);}if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {result.put("dom", serviceName);} else {result.put("dom", NamingUtils.getServiceName(serviceName));}result.put("name", serviceName);result.put("cacheMillis", cacheMillis);result.put("lastRefTime", System.currentTimeMillis());result.put("checksum", service.getChecksum());result.put("useSpecifiedURL", false);result.put("clusters", clusters);result.put("env", env);result.set("hosts", JacksonUtils.createEmptyArrayNode());result.set("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));return result;}Map> ipMap = new HashMap<>(2);ipMap.put(Boolean.TRUE, new ArrayList<>());ipMap.put(Boolean.FALSE, new ArrayList<>());for (Instance ip : srvedIPs) {ipMap.get(ip.isHealthy()).add(ip);}if (isCheck) {result.put("reachProtectThreshold", false);}double threshold = service.getProtectThreshold();if ((float) ipMap.get(Boolean.TRUE).size() / srvedIPs.size() <= threshold) {Loggers.SRV_LOG.warn("protect threshold reached, return all ips, service: {}", serviceName);if (isCheck) {result.put("reachProtectThreshold", true);}ipMap.get(Boolean.TRUE).addAll(ipMap.get(Boolean.FALSE));ipMap.get(Boolean.FALSE).clear();}if (isCheck) {result.put("protectThreshold", service.getProtectThreshold());result.put("reachLocalSiteCallThreshold", false);return JacksonUtils.createEmptyJsonNode();}ArrayNode hosts = JacksonUtils.createEmptyArrayNode();for (Map.Entry> entry : ipMap.entrySet()) {List ips = entry.getValue();if (healthyOnly && !entry.getKey()) {continue;}for (Instance instance : ips) {// remove disabled instance:if (!instance.isEnabled()) {continue;}ObjectNode ipObj = JacksonUtils.createEmptyJsonNode();ipObj.put("ip", instance.getIp());ipObj.put("port", instance.getPort());// deprecated since nacos 1.0.0:ipObj.put("valid", entry.getKey());ipObj.put("healthy", entry.getKey());ipObj.put("marked", instance.isMarked());ipObj.put("instanceId", instance.getInstanceId());ipObj.set("metadata", JacksonUtils.transferToJsonNode(instance.getMetadata()));ipObj.put("enabled", instance.isEnabled());ipObj.put("weight", instance.getWeight());ipObj.put("clusterName", instance.getClusterName());if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {ipObj.put("serviceName", instance.getServiceName());} else {ipObj.put("serviceName", NamingUtils.getServiceName(instance.getServiceName()));}ipObj.put("ephemeral", instance.isEphemeral());hosts.add(ipObj);}}result.replace("hosts", hosts);if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {result.put("dom", serviceName);} else {result.put("dom", NamingUtils.getServiceName(serviceName));}result.put("name", serviceName);result.put("cacheMillis", cacheMillis);result.put("lastRefTime", System.currentTimeMillis());result.put("checksum", service.getChecksum());result.put("useSpecifiedURL", false);result.put("clusters", clusters);result.put("env", env);result.replace("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));return result;
}

查询服务的实例列表直接查询的是注册表,不同namespace,不同group之间的service是无法调用的,同一个service下的不同Cluster可以调用。

相关内容

热门资讯

长鑫科技下周一上市,发行价为8... 来源:@财联社APP微博 财联社7月23日讯,今日晚间,长鑫科技今日发布公告称,公司股票将于2026...
数字人民币开启从“可用”到“好... 证券时报记者 李颖超 当钱包开立规模持续扩容、试点版图不断铺开之后,数字人民币开始尝试切换发展逻辑。...
收评:超4200只个股上涨!电... 每经编辑:彭水萍 7月23日,A股全天走出缩量修复行情,三大指数小幅收红,盘面黄白线分化明显,中小盘...
商务部:将加快修订出台关于外国... 【大河财立方消息】7月23日,国务院新闻办公室举行新闻发布会,介绍2026年上半年商务工作及运行情况...
「AI新世代」智谱算力出鞘:1... 本报(chinatimes.net.cn)记者石飞月 北京报道 这段时间,智谱的股价经历了一番大起大...
刚刚,封死涨停!16.8亿抢筹... 调整已久的碳酸锂火了! 7月23日,锂矿概念持续走强,*ST威领、川能动力、盛新锂能、永杉锂业、国城...
原创 家... 家庭存款高于多少,才算是国内富裕家庭?社会各界看法不一。有人觉得在国内存款超过1000万,才算是富裕...
事关外贸、消费、新能源等领域 ... 商务部今天发布2026年上半年商务工作及运行情况。 商务部副部长鄢东介绍,上半年商务发展总体情况,...
专访美联储前高级经济学家胡捷 每经记者|周逸斐 每经编辑|廖 丹 法国经济预期增长从0.9%下调为0.6%;德国从0.8%下调为0...
90后接盘清北顶流,仅当3个月... *此图由AI生成 作者| 史大郎&猫哥 来源| 是史大郎&大猫财经Pro 今天讲一个非常生猛的资本游...
罕见反超,GDP十强省份又变了 各省份经济半年报陆续公布,备受关注的十强卡位战正式迎来变局。 今年上半年,安徽以27370亿元GDP...
ETF热门榜:中证短融相关ET... 2026年7月22日,非货币类ETF合计成交5510.77亿元,其中有103只ETF成交额破10亿元...
中国银行 :完成发行300亿元... 中国银行发布公告,公司于2026年7月20日在全国银行间债券市场发行减记型无固定期限资本债券,并于2...
中国真的是五省三市养活全国吗? 近几年,网上流传了一个说法,叫“五省三市养全国”。 简单来说,就是2022年,全国仅广东、江苏、浙江...
摩通:华尔街银行业绩强劲提振欧... 观点网讯:7月22日,华尔街大型银行第二季度业绩表现强劲,提振了市场对欧洲投资银行即将开启的财报季的...
广州拟推动房地产纾困和发展 引... 观点网讯:7月22日,“十五五”时期,广州拟切实推动房地产纾困和发展,引导房地产市场企业转型升级。 ...
AI投入成“无底洞”?谷歌资本... 隔夜美股三大指数集体收低,道琼斯工业指数下跌0.01%报52218.58点,标普500指数跌0.14...
新太空经济从“起势”迈向“成势... 当前,全球正处于新一轮科技革命和产业变革加速演进的关键节点,人工智能、合成生物学、固态电池、高端算力...
金价四连涨,水贝又挤满人 每经记者:赵景致 每经编辑:何小桃,廖丹 记者|赵景致 编辑|何小桃 廖丹杜恒峰校对|金冥羽 金价在...
专访微亿智造董事长张志琦:在“... 本报(chinatimes.net.cn)记者石飞月 上海报道 在刚刚过去的2026世界人工智能大会...