Eureka 有個延遲註冊的功能,也就是在服務啟動成功之後不立刻註冊到 Eureka Server,而是延遲一段時間再去註冊,這樣做的主要目的是因為雖然服務啟動成功了,可能還有一些框架或者業務的程式碼沒有初始化完成,可能會導致呼叫的報錯,所以需要延遲註冊。
但是發現,然並卵啊,好像這個延遲註冊並沒有生效,也是開始了排查之路。
首先,延遲註冊的功能主要依賴這兩個引數,eureka.client.initial-instance-info-replication-interval-seconds
代表第一次初始化延遲註冊的時間間隔,eureka.client.instance-info-replication-interval-seconds
則代表後續同步註冊的時間間隔。
eureka.client.initial-instance-info-replication-interval-seconds=40 //預設40秒
eureka.client.instance-info-replication-interval-seconds=30 //預設30秒
我們從原始碼先來看是怎麼做到延遲註冊的,先看 DiscoveryClient
的 initScheduledTasks
,這裡建立了同步註冊到 Eureka Server 的定時任務。
之後呼叫 start
方法建立定時任務,並且延遲 40 秒執行,也就是我們達到的延遲註冊的效果。
預設的第一次註冊,也就是延遲註冊的時間是 40 秒,之後每 30 秒會同步註冊資訊。
但是,即便我們設定了這倆屬性,發現好像沒什麼卵用,接下來我們要排查下到底是為啥捏?
我發現在 InstanceInfoReplica
中存在這樣一段終止當前執行緒池任務,並且直接呼叫 run 方法的存在,猜測失效就是他直接呼叫導致延遲任務沒有生效,因為這個方法的直接呼叫導致延遲註冊壓根就沒效果嘛。
看起來他存在兩個呼叫,第一個是registerHealthCheck
,當存在這個健康檢查什麼玩意兒的時候就會去呼叫onDemandUpdate
。
經過排查我們發現,只要設定了eureka.client.healthcheck.enabled=true
,就會建立 HealthCheckHandler
的範例出來,預設情況下他是false
的,所以應該是對我們沒有影響的。
這裡需要特別說明一下 eureka.client.healthcheck.enabled
的作用,預設 Eureka 根據心跳來決定應用的狀態,如果是這個屬性設定成 true
的話,則是會根據 Spring Boot Actuator 來決定,而不是心跳了。
比如我們可以實現 HealthIndicator
介面,自己寫一個Controller
來動態改變服務的狀態
@RestController
public class ControllerTest {
@Autowired
private HealthChecker healthChecker;
@RequestMapping("/change")
public String test(Boolean flag) {
healthChecker.setUp(new AtomicBoolean(flag));
return "success";
}
}
實現HealthChecker
,這樣會發現啟動、下線服務 Eureka Server 的狀態不會變成 Down,只有通過呼叫介面手動改變應用狀態 Server 的狀態才會發生改變,大家可以自行測試。
@Component
public class HealthChecker extends EurekaHealthIndicator implements HealthIndicator {
private AtomicBoolean up = new AtomicBoolean(true);
public HealthChecker(EurekaClient eurekaClient, EurekaInstanceConfig instanceConfig, EurekaClientConfig clientConfig) {
super(eurekaClient, instanceConfig, clientConfig);
}
@Override
public Health health() {
if(up.get()){
return Health.up().build();
}else{
return Health.down().build();
}
}
第一個問題我們找到了,發現他不是導致我們問題的根因,於是繼續排查。
發現第二個呼叫,在DiscoveryClient
註冊了狀態事件變更的監聽,如果狀態發生變更,也會去呼叫 onDemandUpdate ,影響延遲註冊的效果。
這裡存在一個設定項onDemandUpdateStatusChange
,預設是true
,所以應該是他沒錯了。
進入StatusChangeListener
,找到了一個呼叫。
就是通過setInstanceStatus
方法觸發的事件通知。
這裡存在 6 個呼叫,一一排查,通過原始碼找啊找,最終定位到服務啟動自動裝配的地方,在這裡去修改服務狀態為 UP
,然後觸發事件通知,啟動 start
方法呼叫register
方法。
繼續呼叫,修改應用為上線UP
狀態。
由此我們知道,只要服務啟動成功,就會觸發事件通知,所以這個基本上是啟動成功立刻就會去註冊到 Eureka Server,這就會導致延遲註冊的失效,從啟動紀錄檔也能直觀的看到這個效果。
為了驗證我的猜想,我把這兩個設定同時設定成false
,並且把延遲註冊的時間調整到非常大。
eureka.client.healthcheck.enabled=false
eureka.client.onDemandUpdateStatusChange=false
eureka.client.initial-instance-info-replication-interval-seconds=9999999 //預設40秒
eureka.client.instance-info-replication-interval-seconds=999999 //預設30秒
但是,但是!!!
發現過了幾十秒之後,還是註冊到 Server 了,真的是醉了。。。
那就繼續看吧。
再看下注冊方法,可能不止一個地方存在呼叫,我們發現果然如此,有 3 個地方都呼叫了註冊方法。
第一個呼叫在DiscoveryClient
注入的時候,這個看了下,clientConfig.shouldEnforceRegistrationAtInit()
預設是false
,方法不會進來,不管他了。
那麼繼續看第二個呼叫,第二個呼叫你看renew
方法,這一看我們就知道了,這不就是心跳嗎?!
傳送心跳如果返回NOT_FOUND
,就會去註冊了啊。
感覺已經接近真相了,去找下 Server 心跳的原始碼,根據呼叫的路徑找到原始碼位於InstanceResource
中。
可以看到第一次註冊的時候從登入檔拿到的範例資訊是空的,所以直接返回了 false,就會返回 NOT FOUND 了。
看registry.renew
方法,最終會呼叫到AbstractInstanceRegistry
中,初始化的時候登入檔registry
肯定沒有當前範例的資訊,所以拿到是空的,返回了false,最終就返回了NOT_FOUND
。
因此,雖然我們把這兩個引數都設定成了false
,但是由於心跳預設 30 秒一次,所以最終我們發現設定的超級大的延遲註冊的時間並沒有完全生效。
OK,到此,延遲註冊不生效的原因找到了,我們做一個總結。
預設情況下,設定了延遲註冊的時間並不會生效,因為事件監聽預設是true
,服務啟動之後就會立刻註冊到 Eureka Server。
如果需要延遲註冊生效,必須 eureka.client.healthcheck.enabled
、eureka.client.onDemandUpdateStatusChange
都為false
。
即便我們把所有途徑都封死了,但是傳送心跳的執行緒仍然會去註冊,所以這個延遲註冊的時間最多也不會超過 30 秒,即便設定的延遲時間超過 30 秒。
OK,到此為止,結束,我是艾小仙,歡迎拍磚。