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

十一張圖講透原理,最細(xì)的增量拉取

開發(fā) 前端
如果注冊(cè)表信息很大呢?比如有幾百個(gè)微服務(wù)都注冊(cè)上去了,那一次拉取是非常耗時(shí)的,而且占用網(wǎng)絡(luò)帶寬,性能較差,這種方案是不靠譜的。

[[431338]]

一、前言

上一篇我們講解了客戶端首次獲取注冊(cè)表時(shí),需要從注冊(cè)中心全量拉取注冊(cè)表到本地存著。那后續(xù)如果有客戶端注冊(cè)、下線的話,注冊(cè)表肯定就發(fā)生變化了,這個(gè)時(shí)候客戶端就得更新本地注冊(cè)表了,怎么更新呢?下面我會(huì)帶著大家一起來(lái)看下客戶端第二次(這里代表全量獲取后的下一次)獲取注冊(cè)表的方式。

題外話:之前寫過一篇 Redis 主從同步的架構(gòu)原理,里面也涉及到首次同步和第二次同步,其實(shí)原理也類似,但是 Redis 的主從同步原理要復(fù)雜些。強(qiáng)烈推薦配合著看一波:

鏡 | 5 個(gè)維度深度剖析「主從架構(gòu)」原理

二、增量獲取引發(fā)的問題

上面我們說(shuō)到,當(dāng)?shù)谝淮潍@取全量信息后,本地就有注冊(cè)信息了。那如果 Server 的注冊(cè)表有更新,比如有服務(wù)注冊(cè)、下線,Client 必須要重新獲取一次注冊(cè)表信息才行。

那是否可以重新全量拉取一次呢?

可以是可以,但是,如果注冊(cè)表信息很大呢?比如有幾百個(gè)微服務(wù)都注冊(cè)上去了,那一次拉取是非常耗時(shí)的,而且占用網(wǎng)絡(luò)帶寬,性能較差,這種方案是不靠譜的。

所以我們就需要用增量拉取注冊(cè)信息表的方式,也就是說(shuō)只拉取變化的數(shù)據(jù),這樣數(shù)據(jù)量就比較小了。如下圖所示:

增量獲取注冊(cè)表

從源碼里面我們可以看到,Eureka Client 通過調(diào)用 getAndUpdateDelta 方法獲取增量的變化的注冊(cè)表數(shù)據(jù),Eureka Server 將變化的數(shù)據(jù)返回給 Client。

這里就有幾個(gè)問題:

(1)Client 隔多久進(jìn)行一次增量獲取?

(2)Server 將變化的數(shù)據(jù)存放在哪里?

(3)Client 如何將變化的數(shù)據(jù)合并到本地注冊(cè)表里面?

下面分別針對(duì)上面的幾個(gè)問題進(jìn)行解答。

三、間隔多久同步一次?

3.1 默認(rèn)間隔時(shí)間

默認(rèn)每隔 30 s 執(zhí)行一次同步,如下圖所示:

默認(rèn) 30s 同步一次

這個(gè) 30 s 就是由變量 client.refresh.interval 定義的。

Eureka 每 30 s 會(huì)調(diào)用一個(gè)后臺(tái)線程去拉取增量注冊(cè)表,這個(gè)后臺(tái)線程的名字叫做:cacheRefresh。如下所示:

間隔時(shí)間的源碼

3.2 Client 發(fā)送拉取注冊(cè)表的請(qǐng)求

就是調(diào)用 getDelta 方法,發(fā)送 HTTP請(qǐng)求調(diào)用 jersey 的 restful 接口,然后 Server 端的 Jersey 框架就會(huì)去處理這個(gè)請(qǐng)求了。發(fā)送請(qǐng)求的方法 getDelta 如下所示:

  1. eurekaTransport.queryClient.getDelta(remoteRegionsRef.get()); 
  2. restful 接口的地址就長(zhǎng)這樣: 
  3. http://localhost:8080/v2/apps/delta 

那么 Server 端如何過濾出增量的注冊(cè)表信息呢?我們可以找到這個(gè)方法:getContainerDifferential。如下圖所示:

這個(gè)方法主要干的活就是去獲取最近改變的數(shù)據(jù)。接下來(lái)我們看下最近改變的數(shù)據(jù)存放在哪。

四、變化的數(shù)據(jù)存放在哪?

4.1 數(shù)據(jù)結(jié)構(gòu)

其實(shí)就是放在這個(gè)隊(duì)列里面:recentlyChangedQueue。

它的數(shù)據(jù)結(jié)構(gòu)是一個(gè)并發(fā)安全的鏈表隊(duì)列 ConcurrentLinkedQueue。

鏈表里面存放的元素就是最近變化的注冊(cè)信息 RecentlyChangedItem。 

  1. ConcurrentLinkedQueue<RecentlyChangedItem> 

當(dāng)有客戶端注冊(cè)的時(shí)候,這個(gè)鏈表里面的尾部就會(huì)追加一個(gè)對(duì)象。

關(guān)于 ConcurrentLinkedQueue,還記得我之前寫過的 18 種隊(duì)列嗎?不記得話看下這篇:

45張圖庖丁解牛18種Queue,你知道幾種?

ConcurrentLinkedQueue 是由鏈表結(jié)構(gòu)組成的線程安全的先進(jìn)先出無(wú)界隊(duì)列。如下圖所示:

ConcurrentLinkedQueue原理

4.2 內(nèi)部構(gòu)造

我覺得這個(gè)隊(duì)列的構(gòu)造還是非常值得我們學(xué)習(xí)的,我們來(lái)看下這個(gè)隊(duì)列的構(gòu)造,如下圖所示:

增量數(shù)據(jù)內(nèi)部構(gòu)造

  • 這個(gè)隊(duì)列里面存放的對(duì)象是最近改變的對(duì)象 RecentlyChangedItem。
  • RecentlyChangedItem 存有三個(gè)元素:實(shí)例信息、操作類型和最后更新時(shí)間。
  • 實(shí)例信息:使用 Lease保存一個(gè)客戶端的注冊(cè)表信息,這個(gè)在第四篇講解注冊(cè)表結(jié)構(gòu)已經(jīng)介紹過。
  • 操作類型:當(dāng)有客戶端發(fā)起注冊(cè)、更新注冊(cè)表、下線時(shí),會(huì)設(shè)置 actionType,對(duì)應(yīng)三種枚舉值:新增、更新、刪除。
  • 最后更新時(shí)間:客戶端注冊(cè)信息發(fā)生改變時(shí),需要同時(shí)更新最后更新時(shí)間。

4.3 最近的數(shù)據(jù)

既然上面說(shuō)到是最近改變的數(shù)據(jù)才會(huì)放進(jìn)去,那這個(gè)最近是多近呢?1 分鐘?2分鐘?

通過源碼我們找到了這個(gè)默認(rèn)配置,三分鐘刷新一次,也就是 180s 刷新一次。

那刷新了什么?刷新其實(shí)是會(huì)遍歷這個(gè)隊(duì)列:recentlyChangedQueue。

將隊(duì)列里面的所有元素都遍歷一遍,比對(duì)每個(gè)對(duì)象的最后更新時(shí)間是否超過了三分鐘,如果超過了,就移除這個(gè)元素。如下圖所示:

比較最后更新時(shí)間

當(dāng)元素的最后更新時(shí)間超過 3 分鐘未更新,則移除該元素。如下圖所示:

移除元素

4.4 檢查間隔

Server 端會(huì)將最近 3 分鐘有更新的注冊(cè)信息放入到隊(duì)列中,超過 3 分鐘未更新的數(shù)據(jù)將會(huì)被移除。那么多久會(huì)檢查一次呢?

通過源碼我們找到,每隔 30s 就會(huì)調(diào)用一次檢查任務(wù)。如下圖所示:

檢查間隔

4.5 小結(jié)

  • Client 每隔 30 秒調(diào)用一次增量獲取注冊(cè)表的接口。
  • Server 每隔 30 秒調(diào)用檢查一次隊(duì)列。
  • 如果隊(duì)列中有元素在 3 分鐘以內(nèi)都沒有更新過,則從隊(duì)列中移除該元素。

五、客戶端注冊(cè)表合并

這里有個(gè)問題:客戶端首次拿到的全量注冊(cè)表,存放本地了。第二次拿到的是增量的注冊(cè)表,怎么將兩次的數(shù)據(jù)合并在一起呢?如下圖所示:

注冊(cè)表合并

下面我們來(lái)看看下客戶端注冊(cè)表合并的原理。

當(dāng)客戶端調(diào)用獲取增量注冊(cè)表的請(qǐng)求后,注冊(cè)表會(huì)返回增量信息,然后客戶端就會(huì)調(diào)用本地合并的方法:updateDelta。

合并注冊(cè)表的原理圖如下所示:

合并注冊(cè)表的原理

首先就會(huì)遍歷增量注冊(cè)表,檢查其中的每一項(xiàng),不論 actionType 是新增、刪除還是更新,如果本地本來(lái)就有,則執(zhí)行后續(xù)的類型判斷邏輯。

如果實(shí)例信息的名字在本地不存在則會(huì)先往本地注冊(cè)表新增一個(gè)注冊(cè)信息。然后本地肯定存在注冊(cè)信息了,執(zhí)行后續(xù)的判斷邏輯。

當(dāng)類型字段 actionType 等于新增或更新時(shí),先刪除后增加。

當(dāng)類型字段 actionType 等于刪除時(shí),直接進(jìn)行刪除。

經(jīng)過這一些列的邏輯之后,增量注冊(cè)表和本地注冊(cè)表就合并好了。

六、比對(duì)注冊(cè)表

經(jīng)過重重判斷 + 合并操作,客戶端終于完成了本地注冊(cè)表的刷新,理論上來(lái)說(shuō),這個(gè)時(shí)候客戶端的注冊(cè)表應(yīng)該和注冊(cè)中心的注冊(cè)表一致了。

但是如何確定是一致的呢?這里我們來(lái)考慮幾種方案:

  • 再全量拉取一次注冊(cè)表,和本地注冊(cè)表進(jìn)行比對(duì)。但是既然又要做一次全量拉取,那之前的增量拉取就沒有必要了。
  • 拉取增量注冊(cè)表,Server 返回全量注冊(cè)表的實(shí)例 id,客戶端比對(duì)每個(gè)實(shí)例 id 是否存在,以及檢查本地是否有多余的,如果能匹配上,則認(rèn)為是一致的。但是這里也有一個(gè)問題,對(duì)于新增和更新的注冊(cè)實(shí)例,得把更新的實(shí)例信息的字段一一比對(duì)才能確定是否一致,這就太麻煩了。另外還有一個(gè)致命的問題:如果客戶端因?yàn)榫W(wǎng)絡(luò)故障下線了,上一次最近 3 分鐘的增量數(shù)據(jù)沒有拉取到,那么相當(dāng)于丟失了一次增量數(shù)據(jù),這個(gè)時(shí)候,就不是完整的注冊(cè)表信息了。

有沒有既方便又準(zhǔn)確的比對(duì)方式呢?

有的,那就是哈希比對(duì)。哈希比對(duì)的意思就是將兩個(gè)對(duì)象經(jīng)過哈希算法計(jì)算出兩個(gè) hash 值,如果兩個(gè) hash 值相等,則認(rèn)為這兩個(gè)對(duì)象相等。這種方式在代碼中也非常常見,比如類的 hashcode() 方法。

從源碼中,我們看到 Eureka Server 返回注冊(cè)表時(shí),會(huì)返回一個(gè) hash 值,是將全量注冊(cè)表 hash 之后的值。調(diào)用的是這個(gè)方法:getReconcileHashCode()。

如下圖所示,獲取增量注冊(cè)表的接口,會(huì)返回增量注冊(cè)表和 hashcode。

然后本地注冊(cè)表合并后,再計(jì)算出一個(gè) hashcode,和 Server 返回的 hashcode 進(jìn)行比對(duì),如果一致,說(shuō)明本地注冊(cè)表和 Server 端一致。如果不一致,則會(huì)進(jìn)行一次全量拉取。

上面說(shuō)的原理我們畫一張?jiān)韴D看下就清楚了:

七、總結(jié)

本篇文章可以用一張圖來(lái)做總結(jié),直接上圖:

客戶端注冊(cè)表同步原理

  • 客戶端每隔 30s 獲取一次增量數(shù)據(jù),注冊(cè)中心返回最近 3 分鐘變化的注冊(cè)信息,包含了新注冊(cè)的、更新的和下線的服務(wù)實(shí)例。然后將增量注冊(cè)表 + 全量注冊(cè)表的 hash 值返回。
  • 客戶端將本地注冊(cè)表 + 增量注冊(cè)表進(jìn)行合并。合并完成后,計(jì)算一個(gè) hash 值,和 Server 返回的 hash 值進(jìn)行比對(duì),如果相等,則說(shuō)明客戶端的注冊(cè)表和注冊(cè)中心的注冊(cè)表一致,同步完成。如果不一致,則還需要全量拉取一次。

提個(gè)問題:為什么 hash 比對(duì)會(huì)不一致?答案在文中哦! 

下篇,注冊(cè)中心的緩存架構(gòu)走起!

 

責(zé)任編輯:武曉燕 來(lái)源: 悟空聊架構(gòu)
相關(guān)推薦

2021-09-27 06:29:47

Vue3 響應(yīng)式原理Vue應(yīng)用

2010-01-25 15:15:46

Android傳值

2010-06-10 16:20:37

BGP路由協(xié)議

2023-12-07 19:33:09

Python推導(dǎo)式

2010-01-22 15:25:46

VB.NET Time

2020-03-26 09:18:54

高薪本質(zhì)因素

2016-01-05 11:38:59

Linux內(nèi)核運(yùn)行

2020-05-20 09:55:42

Git底層數(shù)據(jù)

2024-01-29 08:04:48

Golang標(biāo)準(zhǔn)庫(kù)服務(wù)端

2024-02-05 08:50:57

Golang標(biāo)準(zhǔn)庫(kù)客戶端

2023-07-12 20:12:57

前端Monorepo代碼

2020-05-07 08:07:57

synchronize線程

2013-10-10 14:52:53

jQueryDeferred

2010-01-21 14:06:03

VB.NET MyCl

2025-01-13 12:00:00

反射Java開發(fā)

2024-08-07 08:19:13

2023-07-21 14:20:03

ChatGPT神經(jīng)網(wǎng)絡(luò)

2024-09-06 12:52:59

2020-03-12 09:02:34

數(shù)據(jù)思維統(tǒng)計(jì)學(xué)大數(shù)據(jù)

2021-07-28 08:32:58

Go并發(fā)Select
點(diǎn)贊
收藏

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