微服務註冊後,在註冊中心的登錄檔結構是一個map: ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry,假如一個order服務部署了三臺機器,那麼Map的第一個key為服務名稱,第二個map的key是例項編號(instance-id),
InstanceInfo該物件封裝了服務的主要資訊,例如ip 埠 服務名稱 服務的編號等:
如圖:
一、服務的註冊
1.客戶端原始碼(DiscoveryClient類裡面):
2.服務端的原始碼(AbstractInstanceRegistry類):
二、服務的續約(異常下線時,可能無法回撥,此時透過續約+剔除機制實現服務剔除)
1.客戶端原始碼(DiscoveryClient類裡面):透過傳送心跳進行續約,告訴註冊中心我還活著
2.服務端的原始碼(AbstractInstanceRegistry類):
三、服務的下線(客戶端關閉時,主動傳送訊息給註冊中心,註冊中心從登錄檔中將該服務例項刪除)
1.客戶端原始碼(DiscoveryClient類裡面):
2.服務端的原始碼(AbstractInstanceRegistry類):
四、服務的剔除:當註冊中心伺服器一直收不到客戶端的心跳續約超過一定時間限制時,註冊中心會將該服務從登錄檔中剔除,該功能只存在註冊中心
註冊中心原始碼(AbstractInstanceRegistry類):
五、服務的發現:客戶端要向註冊中心拉取註冊列表
1.客戶端原始碼(DiscoveryClient類裡面):
1.1全量拉取:
1.2 增量拉取
2.服務端的原始碼(AbstractInstanceRegistry類):
2.1 註冊中心全量拉取:
2.2 增量拉取:
六 、定時器
1.客戶端會定時向註冊中心傳送心跳進行續約以及定時去註冊中心拉取最新的註冊列表資訊
客戶端原始碼(DiscoveryClient類的構造器裡):省略部分無關代程式碼:
- @Inject
- DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
- Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
- //此處省略了部分程式碼
- try {
- //建立一個執行緒排程器
- scheduler = Executors.newScheduledThreadPool(2,
- new ThreadFactoryBuilder()
- .setNameFormat("DiscoveryClient-%d")
- .setDaemon(true)
- .build());
- //處理心跳的執行緒池
- heartbeatExecutor = new ThreadPoolExecutor(
- 1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>(),
- new ThreadFactoryBuilder()
- .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
- .setDaemon(true)
- .build()
- ); // use direct handoff
- //拉取登錄檔的執行緒池
- cacheRefreshExecutor = new ThreadPoolExecutor(
- 1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>(),
- new ThreadFactoryBuilder()
- .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
- .setDaemon(true)
- .build()
- ); // use direct handoff
- //此處省略部分無關程式碼
- initScheduledTasks(); //呼叫該方法,該方法會執行對應的執行緒池
- }
- */
- private void initScheduledTasks() {
- if (clientConfig.shouldFetchRegistry()) {
- // registry cache refresh timer
- int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
- int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
- cacheRefreshTask = new TimedSupervisorTask(
- "cacheRefresh",
- scheduler,
- cacheRefreshExecutor,
- registryFetchIntervalSeconds,
- TimeUnit.SECONDS,
- expBackOffBound,
- new DiscoveryClient.CacheRefreshThread() //該方法裡面會呼叫拉取登錄檔的方法
- );
- scheduler.schedule(
- cacheRefreshTask,
- registryFetchIntervalSeconds, TimeUnit.SECONDS); //開啟定時任務
- }
- if (clientConfig.shouldRegisterWithEureka()) {
- int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
- int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
- logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
- // Heartbeat timer
- heartbeatTask = new TimedSupervisorTask(
- "heartbeat",
- scheduler,
- heartbeatExecutor,
- renewalIntervalInSecs,
- TimeUnit.SECONDS,
- expBackOffBound,
- new HeartbeatThread();//該執行緒會去呼叫傳送心跳方法
- );
- scheduler.schedule(
- heartbeatTask,
- renewalIntervalInSecs, TimeUnit.SECONDS); //開啟心跳定時任務
- //此處省略部分無關程式碼
- }
//拉取註冊列表的執行緒:
//傳送心跳的執行緒:
2. 註冊中心服務剔除定時器(註冊中心原始碼(AbstractInstanceRegistry類))
總結:eureka註冊中心是去化的,登錄檔是存在記憶體中的,並且客戶端拉取一份登錄檔後,會存在本地快取中,因此即使註冊中心掛了,一樣不影響客戶端相互呼叫
附加: 客戶端如何獲取服務例項demo
綜上所述,其實我們也可以自己寫個簡單的註冊中心,思路如下:
1.建立一個springboot專案,寫個controller類,提供註冊,續約,下線,獲取服務列表四個介面
2. 定義一個例項物件Instance,該物件封裝ip,埠 還有更新時間
3.客戶端呼叫註冊介面,將Instance作為引數傳過來,註冊中心取到對應例項,存到Map<String,Map<String,Instance>> 中
4.客戶端弄個定時器,每個一段時間,呼叫註冊中心的續約方法,將更新例項的修改時間
5.客戶端弄個定時器,每隔一段時間向註冊中心拉取服務,其實就是拉取Map<String,Map<String,Instance>>
6.註冊中心弄個定時器,每隔一段時間遍歷Map<String,Map<String,Instance>>,找出每個例項中的更新時間,加上過期時間,然後跟當前時間比較,看看有沒過期,如果過期就剔除,也就是在map中刪除