自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

微服務(wù):剖析一下源碼,Nacos的健康檢查竟如此簡(jiǎn)單

開(kāi)發(fā) 架構(gòu)
Nacos中臨時(shí)實(shí)例基于心跳上報(bào)方式維持活性,基本的健康檢查流程基本如下:Nacos客戶(hù)端會(huì)維護(hù)一個(gè)定時(shí)任務(wù),每隔5秒發(fā)送一次心跳請(qǐng)求,以確保自己處于活躍狀態(tài)。

[[409195]]

本文轉(zhuǎn)載自微信公眾號(hào)「程序新視界」,作者二師兄。轉(zhuǎn)載本文請(qǐng)聯(lián)系程序新視界公眾號(hào)。

前言

前面我們多次提到Nacos的健康檢查,比如《微服務(wù)之:服務(wù)掛的太干脆,Nacos還沒(méi)反應(yīng)過(guò)來(lái),怎么辦?》一文中還對(duì)健康檢查進(jìn)行了自定義調(diào)優(yōu)。那么,Nacos的健康檢查和心跳機(jī)制到底是如何實(shí)現(xiàn)的呢?在項(xiàng)目實(shí)踐中是否又可以參考Nacos的健康檢查機(jī)制,運(yùn)用于其他地方呢?

這篇文章,就帶大家來(lái)揭開(kāi)Nacos健康檢查機(jī)制的面紗。

Nacos的健康檢查

Nacos中臨時(shí)實(shí)例基于心跳上報(bào)方式維持活性,基本的健康檢查流程基本如下:Nacos客戶(hù)端會(huì)維護(hù)一個(gè)定時(shí)任務(wù),每隔5秒發(fā)送一次心跳請(qǐng)求,以確保自己處于活躍狀態(tài)。Nacos服務(wù)端在15秒內(nèi)如果沒(méi)收到客戶(hù)端的心跳請(qǐng)求,會(huì)將該實(shí)例設(shè)置為不健康,在30秒內(nèi)沒(méi)收到心跳,會(huì)將這個(gè)臨時(shí)實(shí)例摘除。

原理很簡(jiǎn)單,關(guān)于代碼層的實(shí)現(xiàn),下面來(lái)就逐步來(lái)進(jìn)行解析。

客戶(hù)端的心跳

實(shí)例基于心跳上報(bào)的形式來(lái)維持活性,當(dāng)然就離不開(kāi)心跳功能的實(shí)現(xiàn)了。這里以客戶(hù)端心跳實(shí)現(xiàn)為基準(zhǔn)來(lái)進(jìn)行分析。

Spring Cloud提供了一個(gè)標(biāo)準(zhǔn)接口ServiceRegistry,Nacos對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)為NacosServiceRegistry。Spring Cloud項(xiàng)目啟動(dòng)時(shí)會(huì)實(shí)例化NacosServiceRegistry,并調(diào)用它的register方法來(lái)進(jìn)行實(shí)例的注冊(cè)。

  1. @Override 
  2. public void register(Registration registration) {  
  3.    // ... 
  4.    NamingService namingService = namingService(); 
  5.    String serviceId = registration.getServiceId(); 
  6.    String group = nacosDiscoveryProperties.getGroup(); 
  7.  
  8.    Instance instance = getNacosInstanceFromRegistration(registration); 
  9.  
  10.    try { 
  11.       namingService.registerInstance(serviceId, group, instance); 
  12.       log.info("nacos registry, {} {} {}:{} register finished"group, serviceId, 
  13.             instance.getIp(), instance.getPort()); 
  14.    }catch (Exception e) { 
  15.       // ... 
  16.    } 

在該方法中有兩處需要注意,第一處是構(gòu)建Instance的getNacosInstanceFromRegistration方法,該方法內(nèi)會(huì)設(shè)置Instance的元數(shù)據(jù)(metadata),通過(guò)源元數(shù)據(jù)可以配置服務(wù)器端健康檢查的參數(shù)。比如,在Spring Cloud中配置的如下參數(shù),都可以通過(guò)元數(shù)據(jù)項(xiàng)在服務(wù)注冊(cè)時(shí)傳遞給Nacos的服務(wù)端。

  1. spring: 
  2.   application: 
  3.     nameuser-service-provider 
  4.   cloud: 
  5.     nacos: 
  6.       discovery: 
  7.         server-addr: 127.0.0.1:8848 
  8.         heart-beat-interval: 5000 
  9.         heart-beat-timeout: 15000 
  10.        ip-delete-timeout: 30000 

其中的heart-beat-interval、heart-beat-timeout、ip-delete-timeout這些健康檢查的參數(shù),都是基于元數(shù)據(jù)上報(bào)上去的。

register方法的第二處就是調(diào)用NamingService#registerInstance來(lái)進(jìn)行實(shí)例的注冊(cè)。NamingService是由Nacos的客戶(hù)端提供,也就是說(shuō)Nacos客戶(hù)端的心跳本身是由Nacos生態(tài)提供的。

在registerInstance方法中最終會(huì)調(diào)用到下面的方法:

  1. @Override 
  2. public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { 
  3.     NamingUtils.checkInstanceIsLegal(instance); 
  4.     String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName); 
  5.     if (instance.isEphemeral()) { 
  6.         BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance); 
  7.         beatReactor.addBeatInfo(groupedServiceName, beatInfo); 
  8.     } 
  9.     serverProxy.registerService(groupedServiceName, groupName, instance); 

其中BeatInfo#addBeatInfo便是進(jìn)行心跳處理的入口。當(dāng)然,前提條件是當(dāng)前的實(shí)例需要是臨時(shí)(瞬時(shí))實(shí)例。

對(duì)應(yīng)的方法實(shí)現(xiàn)如下:

  1. public void addBeatInfo(String serviceName, BeatInfo beatInfo) { 
  2.     NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo); 
  3.     String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()); 
  4.     BeatInfo existBeat = null
  5.     //fix #1733 
  6.     if ((existBeat = dom2Beat.remove(key)) != null) { 
  7.         existBeat.setStopped(true); 
  8.     } 
  9.     dom2Beat.put(key, beatInfo); 
  10.     executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS); 
  11.     MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size()); 

在倒數(shù)第二行可以看到,客戶(hù)端是通過(guò)定時(shí)任務(wù)來(lái)處理心跳的,具體的心跳請(qǐng)求由BeatTask完成。定時(shí)任務(wù)的執(zhí)行頻次,封裝在BeatInfo,回退往上看,會(huì)發(fā)現(xiàn)BeatInfo的Period來(lái)源于Instance#getInstanceHeartBeatInterval()。該方法具體實(shí)現(xiàn)如下:

  1. public long getInstanceHeartBeatInterval() { 
  2.     return this.getMetaDataByKeyWithDefault("preserved.heart.beat.interval", Constants.DEFAULT_HEART_BEAT_INTERVAL); 

可以看出定時(shí)任務(wù)的執(zhí)行間隔就是配置的metadata中的數(shù)據(jù)preserved.heart.beat.interval,與上面提到配置heart-beat-interval本質(zhì)是一回事,默認(rèn)是5秒。

BeatTask類(lèi)具體實(shí)現(xiàn)如下:

  1. class BeatTask implements Runnable { 
  2.      
  3.     BeatInfo beatInfo; 
  4.      
  5.     public BeatTask(BeatInfo beatInfo) { 
  6.         this.beatInfo = beatInfo; 
  7.     } 
  8.      
  9.     @Override 
  10.     public void run() { 
  11.         if (beatInfo.isStopped()) { 
  12.             return
  13.         } 
  14.         long nextTime = beatInfo.getPeriod(); 
  15.         try { 
  16.             JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled); 
  17.             long interval = result.get("clientBeatInterval").asLong(); 
  18.             boolean lightBeatEnabled = false
  19.             if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) { 
  20.                 lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean(); 
  21.             } 
  22.             BeatReactor.this.lightBeatEnabled = lightBeatEnabled; 
  23.             if (interval > 0) { 
  24.                 nextTime = interval; 
  25.             } 
  26.             int code = NamingResponseCode.OK; 
  27.             if (result.has(CommonParams.CODE)) { 
  28.                 code = result.get(CommonParams.CODE).asInt(); 
  29.             } 
  30.             if (code == NamingResponseCode.RESOURCE_NOT_FOUND) { 
  31.                 Instance instance = new Instance(); 
  32.                 instance.setPort(beatInfo.getPort()); 
  33.                 instance.setIp(beatInfo.getIp()); 
  34.                 instance.setWeight(beatInfo.getWeight()); 
  35.                 instance.setMetadata(beatInfo.getMetadata()); 
  36.                 instance.setClusterName(beatInfo.getCluster()); 
  37.                 instance.setServiceName(beatInfo.getServiceName()); 
  38.                 instance.setInstanceId(instance.getInstanceId()); 
  39.                 instance.setEphemeral(true); 
  40.                 try { 
  41.                     serverProxy.registerService(beatInfo.getServiceName(), 
  42.                             NamingUtils.getGroupName(beatInfo.getServiceName()), instance); 
  43.                 } catch (Exception ignore) { 
  44.                 } 
  45.             } 
  46.         } catch (NacosException ex) { 
  47.             NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}"
  48.                     JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg()); 
  49.              
  50.         } 
  51.         executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS); 
  52.     } 

在run方法中通過(guò)NamingProxy#sendBeat完成了心跳請(qǐng)求的發(fā)送,而在run方法的最后,再次開(kāi)啟了一個(gè)定時(shí)任務(wù),這樣周期性的進(jìn)行心跳請(qǐng)求。

NamingProxy#sendBeat方法實(shí)現(xiàn)如下:

  1. public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException { 
  2.      
  3.     if (NAMING_LOGGER.isDebugEnabled()) { 
  4.         NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString()); 
  5.     } 
  6.     Map<String, String> params = new HashMap<String, String>(8); 
  7.     Map<String, String> bodyMap = new HashMap<String, String>(2); 
  8.     if (!lightBeatEnabled) { 
  9.         bodyMap.put("beat", JacksonUtils.toJson(beatInfo)); 
  10.     } 
  11.     params.put(CommonParams.NAMESPACE_ID, namespaceId); 
  12.     params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName()); 
  13.     params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster()); 
  14.     params.put("ip", beatInfo.getIp()); 
  15.     params.put("port", String.valueOf(beatInfo.getPort())); 
  16.     String result = reqApi(UtilAndComs.nacosUrlBase + "/instance/beat", params, bodyMap, HttpMethod.PUT); 
  17.     return JacksonUtils.toObj(result); 

實(shí)際上,就是調(diào)用了Nacos服務(wù)端提供的"/nacos/v1/ns/instance/beat"服務(wù)。

在客戶(hù)端的常量類(lèi)Constants中定義了心跳相關(guān)的默認(rèn)參數(shù):

  1. static { 
  2.     DEFAULT_HEART_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15L); 
  3.     DEFAULT_IP_DELETE_TIMEOUT = TimeUnit.SECONDS.toMillis(30L); 
  4.     DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5L); 

這樣就呼應(yīng)了最開(kāi)始說(shuō)的Nacos健康檢查機(jī)制的幾個(gè)時(shí)間維度。

服務(wù)端接收心跳

分析客戶(hù)端的過(guò)程中已經(jīng)可以看出請(qǐng)求的是/nacos/v1/ns/instance/beat這個(gè)服務(wù)。Nacos服務(wù)端是在Naming項(xiàng)目中的InstanceController中實(shí)現(xiàn)的。

  1. @CanDistro 
  2. @PutMapping("/beat"
  3. @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) 
  4. public ObjectNode beat(HttpServletRequest request) throws Exception { 
  5.  
  6.     // ... 
  7.     Instance instance = serviceManager.getInstance(namespaceId, serviceName, clusterName, ip, port); 
  8.  
  9.     if (instance == null) { 
  10.         // ... 
  11.         instance = new Instance(); 
  12.         instance.setPort(clientBeat.getPort()); 
  13.         instance.setIp(clientBeat.getIp()); 
  14.         instance.setWeight(clientBeat.getWeight()); 
  15.         instance.setMetadata(clientBeat.getMetadata()); 
  16.         instance.setClusterName(clusterName); 
  17.         instance.setServiceName(serviceName); 
  18.         instance.setInstanceId(instance.getInstanceId()); 
  19.         instance.setEphemeral(clientBeat.isEphemeral()); 
  20.  
  21.         serviceManager.registerInstance(namespaceId, serviceName, instance); 
  22.     } 
  23.  
  24.     Service service = serviceManager.getService(namespaceId, serviceName); 
  25.     // ... 
  26.     service.processClientBeat(clientBeat); 
  27.     // ... 
  28.     return result; 

服務(wù)端在接收到請(qǐng)求時(shí),主要做了兩件事:第一,如果發(fā)送心跳的實(shí)例不存在,則將其進(jìn)行注冊(cè);第二,調(diào)用其Service的processClientBeat方法進(jìn)行心跳處理。

processClientBeat方法實(shí)現(xiàn)如下:

  1. public void processClientBeat(final RsInfo rsInfo) { 
  2.     ClientBeatProcessor clientBeatProcessor = new ClientBeatProcessor(); 
  3.     clientBeatProcessor.setService(this); 
  4.     clientBeatProcessor.setRsInfo(rsInfo); 
  5.     HealthCheckReactor.scheduleNow(clientBeatProcessor); 

再來(lái)看看ClientBeatProcessor中對(duì)具體任務(wù)的實(shí)現(xiàn):

  1. @Override 
  2. public void run() { 
  3.     Service service = this.service; 
  4.     // logging     
  5.     String ip = rsInfo.getIp(); 
  6.     String clusterName = rsInfo.getCluster(); 
  7.     int port = rsInfo.getPort(); 
  8.     Cluster cluster = service.getClusterMap().get(clusterName); 
  9.     List<Instance> instances = cluster.allIPs(true); 
  10.      
  11.     for (Instance instance : instances) { 
  12.         if (instance.getIp().equals(ip) && instance.getPort() == port) { 
  13.             // logging 
  14.             instance.setLastBeat(System.currentTimeMillis()); 
  15.             if (!instance.isMarked()) { 
  16.                 if (!instance.isHealthy()) { 
  17.                     instance.setHealthy(true); 
  18.                     // logging 
  19.                     getPushService().serviceChanged(service); 
  20.                 } 
  21.             } 
  22.         } 
  23.     } 

在run方法中先檢查了發(fā)送心跳的實(shí)例和IP是否一致,如果一致則更新最后一次心跳時(shí)間。同時(shí),如果該實(shí)例之前未被標(biāo)記且處于不健康狀態(tài),則將其改為健康狀態(tài),并將變動(dòng)通過(guò)PushService提供事件機(jī)制進(jìn)行發(fā)布。事件是由Spring的ApplicationContext進(jìn)行發(fā)布,事件為ServiceChangeEvent。

通過(guò)上述心跳操作,Nacos服務(wù)端的實(shí)例的健康狀態(tài)和最后心跳時(shí)間已經(jīng)被刷新。那么,如果沒(méi)有收到心跳時(shí),服務(wù)器端又是如何判斷呢?

服務(wù)端心跳檢查

客戶(hù)端發(fā)起心跳,服務(wù)器端來(lái)檢查客戶(hù)端的心跳是否正常,或者說(shuō)對(duì)應(yīng)的實(shí)例中的心跳更新時(shí)間是否正常。

服務(wù)器端心跳的觸發(fā)是在服務(wù)實(shí)例注冊(cè)時(shí)觸發(fā)的,同樣在InstanceController中,register注冊(cè)實(shí)現(xiàn)如下:

  1. @CanDistro 
  2. @PostMapping 
  3. @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) 
  4. public String register(HttpServletRequest request) throws Exception { 
  5.     // ... 
  6.     final Instance instance = parseInstance(request); 
  7.  
  8.     serviceManager.registerInstance(namespaceId, serviceName, instance); 
  9.     return "ok"

ServiceManager#registerInstance實(shí)現(xiàn)代碼如下:

  1. public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException { 
  2.      
  3.     createEmptyService(namespaceId, serviceName, instance.isEphemeral()); 
  4.     // ... 

心跳相關(guān)實(shí)現(xiàn)在第一次創(chuàng)建空的Service中實(shí)現(xiàn),最終會(huì)調(diào)到如下方法:

  1. public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster) 
  2.         throws NacosException { 
  3.     Service service = getService(namespaceId, serviceName); 
  4.     if (service == null) { 
  5.          
  6.         Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName); 
  7.         service = new Service(); 
  8.         service.setName(serviceName); 
  9.         service.setNamespaceId(namespaceId); 
  10.         service.setGroupName(NamingUtils.getGroupName(serviceName)); 
  11.         // now validate the service. if failed, exception will be thrown 
  12.         service.setLastModifiedMillis(System.currentTimeMillis()); 
  13.         service.recalculateChecksum(); 
  14.         if (cluster != null) { 
  15.             cluster.setService(service); 
  16.             service.getClusterMap().put(cluster.getName(), cluster); 
  17.         } 
  18.         service.validate(); 
  19.          
  20.         putServiceAndInit(service); 
  21.         if (!local) { 
  22.             addOrReplaceService(service); 
  23.         } 
  24.     } 

在putServiceAndInit方法中對(duì)Service進(jìn)行初始化:

  1. private void putServiceAndInit(Service service) throws NacosException { 
  2.     putService(service); 
  3.     service = getService(service.getNamespaceId(), service.getName()); 
  4.     service.init(); 
  5.     consistencyService 
  6.             .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service); 
  7.     consistencyService 
  8.             .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service); 
  9.     Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson()); 

service.init()方法實(shí)現(xiàn):

  1. public void init() { 
  2.     HealthCheckReactor.scheduleCheck(clientBeatCheckTask); 
  3.     for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) { 
  4.         entry.getValue().setService(this); 
  5.         entry.getValue().init(); 
  6.     } 

HealthCheckReactor#scheduleCheck方法實(shí)現(xiàn):

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

延遲5秒執(zhí)行,每5秒檢查一次。

在init方法的第一行便可以看到執(zhí)行健康檢查的Task,具體Task是由ClientBeatCheckTask來(lái)實(shí)現(xiàn),對(duì)應(yīng)的run方法核心代碼如下:

  1. @Override 
  2. public void run() { 
  3.     // ...         
  4.     List<Instance> instances = service.allIPs(true); 
  5.      
  6.     // first set health status of instances: 
  7.     for (Instance instance : instances) { 
  8.         if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) { 
  9.             if (!instance.isMarked()) { 
  10.                 if (instance.isHealthy()) { 
  11.                     instance.setHealthy(false); 
  12.                     // logging... 
  13.                     getPushService().serviceChanged(service); 
  14.                     ApplicationUtils.publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance)); 
  15.                 } 
  16.             } 
  17.         } 
  18.     } 
  19.      
  20.     if (!getGlobalConfig().isExpireInstance()) { 
  21.         return
  22.     } 
  23.      
  24.     // then remove obsolete instances: 
  25.     for (Instance instance : instances) { 
  26.          
  27.         if (instance.isMarked()) { 
  28.             continue
  29.         } 
  30.          
  31.         if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) { 
  32.             // delete instance 
  33.             deleteIp(instance); 
  34.         } 
  35.     } 

在第一個(gè)for循環(huán)中,先判斷當(dāng)前時(shí)間與上次心跳時(shí)間的間隔是否大于超時(shí)時(shí)間。如果實(shí)例已經(jīng)超時(shí),且為被標(biāo)記,且健康狀態(tài)為健康,則將健康狀態(tài)設(shè)置為不健康,同時(shí)發(fā)布狀態(tài)變化的事件。

在第二個(gè)for循環(huán)中,如果實(shí)例已經(jīng)被標(biāo)記則跳出循環(huán)。如果未標(biāo)記,同時(shí)當(dāng)前時(shí)間與上次心跳時(shí)間的間隔大于刪除IP時(shí)間,則將對(duì)應(yīng)的實(shí)例刪除。

小結(jié)

 

通過(guò)本文的源碼分析,我們從Spring Cloud開(kāi)始,追蹤到Nacos Client中的心跳時(shí)間,再追蹤到Nacos服務(wù)端接收心跳的實(shí)現(xiàn)和檢查實(shí)例是否健康的實(shí)現(xiàn)。想必通過(guò)整個(gè)源碼的梳理,你已經(jīng)對(duì)整個(gè)Nacos心跳的實(shí)現(xiàn)有所了解。關(guān)注我,持續(xù)更新Nacos的最新干貨。

 

責(zé)任編輯:武曉燕 來(lái)源: 程序新視界
相關(guān)推薦

2023-03-02 07:20:10

GRPC服務(wù)健康檢查協(xié)議

2023-03-01 08:33:37

gRPC健康檢查代碼

2023-03-03 08:19:35

KubernetesgRPC

2021-06-29 21:36:21

微服務(wù)Nacos日志

2022-02-28 07:40:23

Nacos注冊(cè)中心客戶(hù)端

2017-08-25 10:20:46

Docker容器機(jī)制

2023-02-18 13:34:14

Nacos健康檢查機(jī)制

2021-07-15 10:25:15

集群節(jié)點(diǎn)檢查

2023-10-14 15:36:14

PodKubernetes

2024-02-27 17:30:11

2020-12-07 06:29:13

SpringBoot

2023-05-09 07:34:25

Docker健康檢查方式

2022-09-07 09:19:49

Docker健康檢查

2024-09-04 10:44:19

2022-07-08 08:37:23

Nacos服務(wù)注冊(cè)動(dòng)態(tài)配置

2021-02-26 13:59:41

RocketMQProducer底層

2021-09-18 16:10:48

Spring BootJava微服務(wù)

2019-10-11 09:39:44

HTTP調(diào)用系統(tǒng)

2017-05-03 16:36:32

Android圖片動(dòng)畫(huà)

2021-08-02 07:57:03

注冊(cè)Nacos源碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)