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

16 圖 | Nacos 架構(gòu)原理①:一條注冊請求會經(jīng)歷什么?

開發(fā) 前端
本文通過發(fā)起一條注冊請求,講解了 Nacos 客戶端如何隨機(jī)選擇節(jié)點(diǎn)、Nacos Server 如何將請求進(jìn)行路由轉(zhuǎn)發(fā)、Nacos Server 如何存儲注冊實例。

這次我們來聊下 Nacos 的注冊服務(wù)的底層原理。

Nacos 作為注冊中心,用來接收客戶端(服務(wù)實例)發(fā)起的注冊請求,并將注冊信息存放到注冊中心進(jìn)行管理。

那么一條注冊請求到底會經(jīng)歷哪些步驟呢?

知識點(diǎn)預(yù)告

先上一張整體的流程圖:

  • 集群環(huán)境:如果是 Nacos 集群環(huán)境,那么拓?fù)浣Y(jié)構(gòu)是什么樣的。
  • 組裝請求:客戶端組裝注冊請求,下一步對 Nacos 服務(wù)發(fā)起遠(yuǎn)程調(diào)用。
  • 隨機(jī)節(jié)點(diǎn):客戶端隨機(jī)選擇集群中的一個 Nacos 節(jié)點(diǎn)發(fā)起注冊,實現(xiàn)負(fù)載均衡。
  • 路由轉(zhuǎn)發(fā):Nacos 節(jié)點(diǎn)收到注冊請求后,看下是不是屬于自己的,不是的話,就進(jìn)行路由轉(zhuǎn)發(fā)。
  • 處理請求:轉(zhuǎn)發(fā)給指定的節(jié)點(diǎn)后,該節(jié)點(diǎn)就會將注冊請求中的實例信息解析出來,存到自定義的內(nèi)存結(jié)構(gòu)中。
  • 最終一致性:通過 Nacos 自研的 Distro 協(xié)議執(zhí)行延遲異步任務(wù),將注冊信息同步給集群中的其他節(jié)點(diǎn),保證了數(shù)據(jù)的最終一致性。
  • 異步重試:如果注冊失敗,客戶端將會切換 Nacos 節(jié)點(diǎn),再次發(fā)起注冊請求,保證高可用性。

這些知識點(diǎn)里面還有很多細(xì)節(jié),我會通過畫圖 + 源碼剖析的方式給大家解答。如果遇到源碼看不太懂的地方,可以多看下我畫的圖,然后翻下源碼,對照著一起看。

小 Tip:本文使用的 Nacos 版本:2.0.4。

一、源頭:發(fā)起注冊

1.1 閱讀源碼的小技巧

上篇我們講到加上一個注解 @EnableDiscoveryClient 就可以使服務(wù)自動注冊到 Nacos。

那么這個發(fā)起注冊的地方到底在哪呢?注冊信息又是長什么樣的呢?

告訴大家一個看源碼的小技巧,拿到源碼后,不是直接各個文件都看一篇,而是先看源碼中帶的 example 文件夾。如下圖所示,找到 example 的 App 類,里面就有發(fā)起注冊的實例代碼。如下圖所示:

當(dāng)然,我們也可以通過官網(wǎng)給的 curl 命令發(fā)起 HTTP 請求:

curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.11&port=8080'

留個問題:我們都是加一個 Nacos 注解 @EnableDiscoveryClient,就會自動把服務(wù)實例注冊到 Nacos,這個是怎么做到的?

1.2 發(fā)起注冊的流程圖

先來看一下代碼的流程圖:

跟著這個流程圖,我們 debug 來看下。

1.3 組裝注冊的實例信息

入口的核心代碼如下圖所示,它會組裝注冊的實例信息,放到一個 instance 變量里面:

通過代碼調(diào)試,我們可以看到里面的實例信息長這樣:

1.4 組裝注冊請求 request

發(fā)起注冊的核心方法是 doRegisterService(),組裝的 request 如下圖所示,里面有之前組裝的實例信息 instance,還有指定的 namespace(Nacos 的命名空間)、serviceName(服務(wù)名),groupName(Nacos 的分組)。

發(fā)起注冊的源碼

1.5 發(fā)起遠(yuǎn)程調(diào)用

requestToServer() 方法里面會調(diào)用 RpcClient 的 request() 方法:

response = this.currentConnection.request(request, timeoutMills);

就是向 Nacos 發(fā)起遠(yuǎn)程調(diào)用,如果是 Nacos 集群,則是向集群中的某個 Nacos 節(jié)點(diǎn)發(fā)起遠(yuǎn)程調(diào)用。

接下來我們看下客戶端是如何選擇一個 Nacos 節(jié)點(diǎn)進(jìn)行注冊的。

二、集群環(huán)境:分布式的前提

如果是 Nacos 集群環(huán)境,客戶端會隨機(jī)選擇一個 Nacos 節(jié)點(diǎn)發(fā)起注冊。

2.1 搭建好一套Nacos 集群環(huán)境

為了講解客戶端是如何注冊到 Nacos 集群環(huán)境的底層原理,我在本地搭建了一個 Nacos 集群環(huán)境,有 3 個 Nacos 服務(wù),它們的 IP 相同,端口號不同。

192.168.10.197:8848
192.168.10.197:8858
192.168.10.197:8868


集群環(huán)境

然后服務(wù) A 和服務(wù) B 都是配置了 Nacos 集群的 IP 和 端口號的,配置如下所示:

spring.cloud.nacos.discovery.server-addr
=192.168.10.197:8848,192.168.10.197:8858,192.168.10.197:8868

整體的結(jié)構(gòu)如下圖所示,服務(wù) A 和 服務(wù) B 都往 Nacos 集群進(jìn)行注冊。

服務(wù) A 和 B 注冊到集群

但是里面有一個問題:服務(wù) A 注冊時,是向所有 Nacos 節(jié)點(diǎn)發(fā)起注冊呢?還是只向其中一個節(jié)點(diǎn)發(fā)起注冊?如果只向一個節(jié)點(diǎn)注冊,要向哪個節(jié)點(diǎn)注冊呢?

答案:在 Client 發(fā)起注冊之前,會有一個后臺線程隨機(jī)拿到 Nacos 集群服務(wù)列表中的一個地址。

Nacos 為什么會這樣設(shè)計?

這其實就是一個負(fù)載均衡的思想在里面,每個節(jié)點(diǎn)都均勻的分?jǐn)傉埱蟆?/p>

保證高可用,當(dāng)某個節(jié)點(diǎn)宕機(jī)后,重新拿到其他的 Nacos 節(jié)點(diǎn)來建立連接。

接下來我們看下服務(wù) A 是怎么隨機(jī)拿到一個 Nacos 節(jié)點(diǎn)的。

三、隨機(jī)節(jié)點(diǎn):平等的世界

我們來看下客戶端是如何隨機(jī)選擇一個節(jié)點(diǎn)的,流程圖如下:

那么如何找到這些代碼邏輯呢?思路是怎么樣的?

我們之前講過,RpcClient 會發(fā)起 request 請求,用的是和 Nacos 建立 currentConnection 連接來發(fā)起調(diào)用,代碼如下:

// 發(fā)起調(diào)用
response = this.currentConnection.request(request, timeoutMills);

這個 currentConnection 是客戶端和 Nacos 集群中的某個節(jié)點(diǎn)建立的連接,我們找下它在哪里賦值的。代碼如下:

// 拿到 Nacos 節(jié)點(diǎn)信息
serverInfo = recommendServer.get() == null ? nextRpcServer() : recommendServer.get();
// 連接 Nacos 節(jié)點(diǎn)
connectToServer = connectToServer(serverInfo);
// 賦值 currentConnection
this.currentConnection = connectToServer;

而連接的信息是通過參數(shù) serverInfo 傳進(jìn)去的,所以我們再看下 serverInfo 在哪里賦值的。

這個 nextRpcServer() 方法里面會拿到一個隨機(jī)的 Nacos 地址:

// 一個 int 隨機(jī)數(shù),范圍 [0 ~ Nacos 個數(shù))
currentIndex.set(new Random().nextInt(serverList.size()));
// index 自增 1
int index = currentIndex.incrementAndGet() % getServerList().size();
// 返回 Nacos 地址
return getServerList().get(index);

小結(jié):客戶端生成一個隨機(jī)數(shù),然后通過這個隨機(jī)數(shù)從 Nacos 服務(wù)列表中拿到一個 Nacos 服務(wù)地址返回給客戶端,然后客戶端通過這個地址和 Nacos 服務(wù)建立連接。Nacos 服務(wù)列表中的節(jié)點(diǎn)都是平等的,隨機(jī)拿到的任何一個節(jié)點(diǎn)都是可以用來發(fā)起調(diào)用的。

四、路由轉(zhuǎn)發(fā):不是我的菜

4.1 發(fā)起和轉(zhuǎn)發(fā)請求的流程

為了演示發(fā)起注冊的流程,我在這里模擬了一個注冊請求。

用的是 curl 命令,對 Nacos 節(jié)點(diǎn)(127.0.0.1:8848)發(fā)起注冊請求:

curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.11&port=8080'

請求 URL:/nacos/v1/ns/instance

請求參數(shù):

  • serviceName=nacos.naming.serviceName
  • ip=20.18.7.11
  • port=8080'

之前我們講到,Nacos 的有多個節(jié)點(diǎn)可以分別處理請求,當(dāng)節(jié)點(diǎn)發(fā)現(xiàn)這個請求不是屬于自己的,就會進(jìn)行轉(zhuǎn)發(fā)。

如下圖所示:

服務(wù) A 隨機(jī)選擇一個 Nacos 節(jié)點(diǎn)(圖中為 Nacos1)發(fā)起注冊請求,請求參數(shù)中包含了實例信息,Nacos 1 根據(jù)實例信息 hash + 取模拿到正確的節(jié)點(diǎn),如果不屬于自己,則將請求轉(zhuǎn)發(fā)給其他節(jié)點(diǎn)(圖中為 Nacos2)。

那么路由轉(zhuǎn)發(fā)的細(xì)節(jié)是怎么樣的?這個就涉及到 Distro 協(xié)議了,我們接著往下看。

4.1 路由轉(zhuǎn)發(fā)的邏輯

其實 Nacos 節(jié)點(diǎn)的路由轉(zhuǎn)發(fā)邏輯比較簡單,先來看下流程圖:

步驟如下:

  • ① Nacos 節(jié)點(diǎn)從客戶端發(fā)起的 request 中拿到客戶端的實例信息生成 distroTag,如 IP + port 或 service name。
  • ② Nacos 根據(jù) distroTag 生成 hash 值。
  • ③ 用 hash 值對 Nacos 節(jié)點(diǎn)數(shù)進(jìn)行取余,拿到余數(shù),比如 0、1、2、3。
  • ④ 根據(jù)余數(shù)從 Nacos 節(jié)點(diǎn)列表中拿到指定的節(jié)點(diǎn)地址。

我沒看懂的點(diǎn):我這里啟動了三個 Nacos 節(jié)點(diǎn),如下圖所示的 三個 Running 節(jié)點(diǎn)。但是為什么 Nacos 的 ServersList 會多了一個 192.168.10.197:8848的節(jié)點(diǎn)?

IDEA 啟動了三個 nacos 節(jié)點(diǎn)

nacos 控制臺有四個節(jié)點(diǎn)

4.2 路由轉(zhuǎn)發(fā)源碼分析

入口文件是 DistroFilter.java:

naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java

請求會先到 DistroFilter 類的 doFilter() 方法,拿到正確的節(jié)點(diǎn)地址后,將請求轉(zhuǎn)發(fā)出去。

獲取需要轉(zhuǎn)發(fā)節(jié)點(diǎn)地址的代碼如下:

// 找到 Nacos 集群中的目標(biāo)節(jié)點(diǎn)
final String targetServer = distroMapper.mapSrv(distroTag);

// mapSrv 方法會先 hash,然后再取模,responsibleTag的值類似這樣:"20.18.7.11:8080"
int index = distroHash(responsibleTag) % servers.size();

// distroHash 方法里面會對 客戶端的 ip+port 字符串或者服務(wù)名字符串 進(jìn)行 hash
Math.abs(responsibleTag.hashCode() % Integer.MAX_VALUE);

不論是自己處理注冊請求還是轉(zhuǎn)發(fā)給其他節(jié)點(diǎn)來處理,都會把實例信息存儲起來,那么是如何進(jìn)行存儲的?

五、處理請求:快到碗里來

Nacos 目前有兩個版本,v1 和 v2,如果是 v1,則是 instanceController 來處理注冊請求,否則用 instanceControllerV2。本篇我們只講解 v1 版本是怎么處理請求的。

先上流程圖:

添加實例信息的流程

測試用的發(fā)起注冊的命令:

curl -X POST 'http://127.0.0.1:8858/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.11&port=8080'

核心代碼就是這個:

服務(wù)端注冊實例的方法

首先有一個 synchronized 鎖,然后執(zhí)行 put 操作將臨時的實例信息存放起來,所以重點(diǎn)看下 這個 consistencyService.put() 方法做了什么事情。

先看下源碼:

onPut(key, value);
// 開啟 1s 的延遲任務(wù),將數(shù)據(jù)同步給其他 Nacos 節(jié)點(diǎn)
distroProtocol.sync(new DistroKey(key,KeyBuilder.INSTANCE_LIST_KEY_PREFIX),DataOperation.CHANGE,
DistroConfig.getInstance().getSyncDelayMillis());

這里面做了三件事情:

  • ① 將實例信息存放到內(nèi)存緩存 ConcurrentHashMap 里面。
  • ② 添加一個任務(wù)到 BlockingQueue 隊列里面,這個任務(wù)就是將最新的實例列表通過 UDP 的方式推送給所有客戶端(服務(wù)實例),這樣客戶端就拿到了最新的服務(wù)實例列表。沒想到吧,計算機(jī)網(wǎng)絡(luò)的知識終于用上了~
  • ③ 開啟 1s 的延遲任務(wù),將數(shù)據(jù)通過給其他 Nacos 節(jié)點(diǎn)。

注意:針對第二點(diǎn)和第三點(diǎn),屬于 Distro 一致性協(xié)議的一部分,里面的內(nèi)容還比較多,我們放到下一講專門來講。

下一講知識點(diǎn)預(yù)告:

  • 這里的存儲實例和同步的方式和 Eureka 有什么區(qū)別?Eureka 用的三層緩存架構(gòu),Nacos 用的 CopyOnWrite 技術(shù)。
  • 如何推送給所有客戶端的?UDP 方式。
  • 如何同步給 Nacos 其他節(jié)點(diǎn)的?Distro 一致性協(xié)議。

六、總結(jié)

本文通過發(fā)起一條注冊請求,講解了 Nacos 客戶端如何隨機(jī)選擇節(jié)點(diǎn)、Nacos Server 如何將請求進(jìn)行路由轉(zhuǎn)發(fā)、Nacos Server 如何存儲注冊實例。

另外本文用到了集群環(huán)境,關(guān)于如何搭建和 debug 集群環(huán)境,感興趣的可以留言,后續(xù)補(bǔ)上這部分的講解。

一條注冊請求的核心流程:

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

2021-06-15 10:46:51

HTTPS網(wǎng)絡(luò)協(xié)議TCP

2023-01-16 18:32:15

架構(gòu)APNacos

2023-10-06 15:29:07

MySQL數(shù)據(jù)庫更新

2025-03-11 00:22:00

DeepSeekAI圖片

2024-01-02 22:47:47

Nacos注冊中心節(jié)點(diǎn)

2010-08-25 15:08:22

經(jīng)歷

2024-08-29 12:37:11

2019-11-06 15:16:12

16GB8GB內(nèi)存

2022-05-31 13:58:09

MySQL查詢語句

2010-04-13 16:57:01

2019-03-28 10:09:49

內(nèi)存CPU硬盤

2011-03-21 17:19:12

LAMPUbuntu

2022-04-02 15:55:21

中國互聯(lián)網(wǎng)互聯(lián)網(wǎng)公司發(fā)展史

2025-01-09 08:32:50

2021-04-16 07:04:53

SQLOracle故障

2011-06-24 09:20:04

編程語言

2011-05-12 14:43:57

MYSQL

2011-12-29 20:58:46

Windows Pho

2020-03-03 11:35:40

PythonMySQL數(shù)據(jù)

2021-09-28 13:32:24

innoDB架構(gòu)MySQL
點(diǎn)贊
收藏

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