微服務註冊後,在註冊中心的登入檔結構是一個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中刪除