唐太宗把微服務的“心跳機制”玩到了極致!
你好,我是悟空。
唐朝第二位皇帝唐太宗為了擴張領土,到處攻打周邊的小國,即使有不服的小國也被唐太宗打服了。這些小國后來就都需要向唐太宗朝貢。
朝貢就是朝拜和進貢。是兩國或者說是兩個政府之間的一種承認對方尊卑關系的禮節性外交。
唐朝朝貢圖,來源百度百科
“貞觀之治” 說的就是當時唐朝的鼎盛時期, 周圍小國都被打趴下了,國內繁榮發展,以十分驚人的速度成為世界頂級強國。唐人街中的“唐”就是說的唐朝,足以說明唐朝對世界的影響。
小國定期向唐朝進行朝貢這不就是微服務的心跳機制嗎?
他們是在告訴唐朝,我還是服你管教的。然后唐朝就會把這些小國的名字、地址、服飾外貌等特征放到一個朝貢國列表中。萬一哪天這些小國不服管了,就把他們從列表中移除掉,后期可能還會攻打他們~
下面是一張多國朝貢的示例圖:
朝貢示例圖
在微服務領域,心跳機制出現得太頻繁了,比如 Eureka、Naocs 中的客戶端和服務端的服務續約、Redis 的主從復制等等,其實原理都很相似。
本篇會通過 Eureka 中的服務續約功能作為示例來剖析心跳機制。
對于 Eureka,會涉及到兩個端,客戶端和服務端??蛻舳司拖喈斢谖覀兊挠唵畏铡⑸唐贩盏取6?Eureka 服務端則是指 Eureka 注冊中心這個服務。而保持續約就是客戶端隔一段時間就向服務端發送一次心跳,告訴 Eureka 服務端自己的狀態是存活的。
主要涉及以下知識點:
- ① 誰發送的心跳請求?
- ② 多久發送一次?
- ③ 如何發送的?
- ④ 如何接收心跳請求的?
- ⑤ 接收后做了什么事情?
誰發送的心跳請求
Eureka 采用的是客戶端發送心跳請求給 Eureka 服務端。如下圖所示:
上圖中有三個微服務:訂單服務、商品服務、優惠券服務,都已經成功注冊到 Eureka 服務端了(注冊中心)。
然后每個微服務自己會單獨發送心跳請求給注冊中心。
多久發送一次
DIscoveryClient 初始化時,會調度一些定時任務。Eureka 初始化了發送心跳請求的線程池 heartbeatExecutor,用來創建發送心跳的線程 HeartbeatThread。原理如圖所示:
線程池 heartbeatExecutor 源碼如下所示:
線程池
線程池有核心參數:
- maximumPoolSize:最大線程數。線程池允許創建的最大線程數。
- corePoolSize:核心線程數。當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閑的核心線程能夠執行新任務也會創建線程,等到 需要執行的任務數大于線程池基本大小時就不再創建。如果調用了線程池的 prestartAllCoreThreads() 方法,則線程池會提前創建并啟動所有基本線程。
- keepAliveTime:線程活動保持時間 ,線程池的工作線程空閑后,保持存活的時間。
- runnableTaskQueue:任務隊列,用于保存等待執行的任務的阻塞隊列。有四種:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue。
然后將這個線程池用來執行定時調度任務,源碼如下所示,在定時任務開始后,延遲 30s 開始執行發送心跳請求,然后每隔 30秒執行一次發送心跳請求。這里可以看到 new 了一個 HeartbeatThread 線程。更多線程相關知識,請看這篇:多線程核心知識點
定時任務
如何發送心跳請求的?
HeartbeatThread 線程繼承自 Runnable 類,實現了 run 方法,這個里面就會執行發送心跳請求的具體邏輯了。
直接進到 renew() 方法里面,核心邏輯就這一行:
eurekaTransport.registrationClient.sendHeartBeat(
instanceInfo.getAppName(),
instanceInfo.getId(),
instanceInfo,
null);
調用 EurekaHttpClient 的 sentHeartBeat 方法,將實例信息發送給注冊信息。
拼接的請求 URL 示例如下:
http://localhost:8080/v2/apps/order/i-000000-1
而且這個請求是 PUT 請求。
如何接收心跳請求的?
請求從客戶端發出心跳請求后,服務端就要接收這個請求了。
負責接受請求的類為 ApplicationsResource,它相當于 MVC 中的 Controller。
根據請求的 URL 格式和請求方式(PUT),我們可以找到服務端的方法為 InstanceResource.renewLease()。
ApplicationsResource->ApplicationResource->InstanceResource
接收后做了什么事情
里面的核心代碼就是 renew 方法,將實例的一個字段給更新了,這個字段叫做 lastupdateTimestamp,也就是最后更新時間。
public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
心跳機制
這個實例其實是從服務端注冊表 registry 中拿到的,它是一個 ConcurrentHashmap,實例名當做 key,來獲取 value(實例),也就是說實例信息是存在內存中的。
拿到的是一個 Lease 實例,數據結構是這樣的:Lease,它有一個 volatile 修飾的字段 lastUpdateTimestamp。通過更新這個字段來記錄實例信息確實存活著在,而且剛剛還跟 Eureka 通信了。
這就像古代唐朝的朝貢,唐朝周邊的小國是需要定期進貢給唐朝的,目的是告訴唐朝,我現在還是依附唐朝的。
那么有了這個字段更新,Eureka Server 自身還會有個定時任務,去檢查服務實例的最后更新時間,如果過期了,則認為該實例狀態異常,需要進行服務下線,這個是下一篇要講的內容。