rnacos實現raft和類distro協議,支持集羣部署

1. rnacos 簡介

rnacos是一個用rust實現的nacos服務。

rnacos是一個輕量、 快速、穩定、高性能的服務;包含註冊中心、配置中心、web管理控制檯功能,支持單機、集羣部署。

rnacos設計上完全兼容最新版本nacos面向client sdk 的協議(包含1.x的http OpenApi,和2.x的grpc協議), 支持使用nacos服務的應用平遷到 rnacos。

rnacos相較於java nacos來說,是一個提供相同功能,啓動更快、佔用系統資源更小、性能更高、運行更穩定的服務。

2. rnacos支持集羣部署

rnacos之前只支持單機部署,不能水平擴容,同時存在單點穩定性問題,不太合適用於生產環境。所以rnacos一直有計劃開發支持集羣部署的功能。

目前rnacos 0.3.1版本已支持集羣部署。其中配置中心通過raft協議支持集羣部署,註冊中心通過類distro協議支持集羣部署。

rnacos主要功能模塊:

2.1 爲什麼在同一個應用中,配置中心、註冊中心需要實現兩個不同的協議支持集羣部署?

主要因爲配置中心和註冊中心的特點不一樣。

配置中心的數據需要持久化,在多個服務節點中的數據需要強一致,raft是一個邏輯完備的分頁式共識協議。實現raft協議只要大於半數的節點正常,就可以正常提供服務。 同時實例raft協議就相當於實現一個分頁式存儲,配置中心可以不需要額外依賴 mysql 等外部數據庫,部署依賴更簡單。 所以配置中心選擇通過raft協議支持集羣部署。

註冊中心的數據主要是臨時的服務實例數據,這類數據不需要持久化,不追求多個服務節點中的數據強一致。同時註冊中心更關注在部分節點異常時能提供完整的服務,更觀注集羣的讀寫性能。所以註冊中心不選擇 raft 協議,而是通過類 distro協議支持集羣部署。

功能模塊協議的對比:

模塊 協議 寫性能 讀性能 數據一致性 容錯率
配置中心 raft 一般(只有主節點可寫) 高,每個節點都可讀 強一致 一般,大於半數節點正常則可以正常提供服務
註冊中心 distro 高(每個節點都可寫) 高,每個節點都可讀 一般 高,一個節點能不依賴其它依賴提供服務

2.2 配置中心raft協議

raft協議的主要邏輯:

  1. 節點區分角色:leader(主節點),follower(從節點),candidate(選舉節點);
  2. 穩定狀態是一個主節點,多個從節點;
  3. 主節點負責寫入,寫入時需要先把寫入日誌同步到其它節點,超過半數節點寫入日誌成功後才能提交日誌到狀態機。
  4. 主節點需要定時發心跳到從節點,從節點如果超時未收到心跳,則會發起選舉。選舉時收到超過半數節點的同意,就可以切換成主節點。

具體協議可以參考 raft協議論文

rnacos 接入 raft的主要邏輯:

  1. 基於 async-raft 庫實現raft協議,主要實現網絡層和存儲層。在 rnacos中存儲層的狀態機就是配置中心。
  2. 配置中心接入raft 協議的狀態機,由 raft 狀態機驅動更新配置中心的內容。

rnacos一個三節點的配置中心請求處理示例:

寫入:

  1. 客戶端隨機向一個節點發起一個更新配置請求
  2. 在請求入口層加一個raft路由判斷,如果本節點是主節點則處理,否則路由到指定主節點處理
  3. 主節點寫入請求到raft日誌
  4. 將請求同步到其它從節點
  5. 如果超過半數節點寫入日誌成功(包含自身),則提交請求日誌到狀態機中,配置寫入配置中心。(其它從節點的提交在下次日誌同步或心跳時提交)
  6. 返回處理結果

請求:

  1. 客戶端隨機向一個節點發起一個查詢配置請求
  2. 收到請求的節點和單機處理一樣,直接查詢本節點配置中心數據返回。

2.3 註冊中心類distro協議

協議主要邏輯:

  1. 每個節點有全量的數據,都可提供註冊信息查詢服務。
  2. 註冊中心每個節點平等,按hash劃分每個節點負責的內容;節點對負責的服務可寫,否則轉發到對應負責的節點處理。
  3. 通過 grpc協議註冊的服務,接收的節點直接處理。
  4. 一個節點更新服務實例信息後再同步給其它節點。

具體協議可以參考java nacos 的distro協議實現 。
rnacos 和 java版主體邏輯相同,但實現的細節有些區別。

rnacos一個三節點的註冊中心請求處理示例:

http 寫入:

  1. 客戶端隨機向一個節點發起一個註冊服務實例請求
  2. 請求跳過服務路由判斷,如果服務路由的節點是本節點則處理,否則路由到指定的其它節點處理
  3. 收到本節點負責的服務實例請求,把請求註冊到註冊中心中
  4. 返回處理結果
  5. 異步同步更新的數據到其它節點

grpc 寫入(不路由,本節點直接處理):

  1. 客戶端隨機向一個節點發起grpc長鏈接
  2. 客戶端發起一個註冊服務實例請求
  3. 像單機一樣,把請求註冊到註冊中心中
  4. 返回處理結果
  5. 異步同步更新的數據到其它節點

查詢:

  1. 客戶端隨機向一個節點發起一個查詢服務信息請求
  2. 收到請求的節點和單機處理一樣,直接查詢本節點註冊中心數據返回。

爲什麼http的寫入與grpc寫入的路由邏輯不同?

因爲grpc的心跳是按長鏈接來處理,一個客戶端的鏈接段開,則這個鏈接的所用請求都失效。【高效】
然後 http 的實例註冊是無狀態的,只能通過定時器按註冊時間更新實例的狀態;同時註冊中心中實例是按服務分類維護的,所以 http 註冊的實例需要按服務做路由,這樣才能支持不同的節點負責不同範圍的服務。【低效】

所以在註冊中心使用grpc協議的性能會比http協議性能好很多。

3. 性能與容量

rnacos 支持集羣后其性能與容量的水位是怎樣的呢?

下面給出一組在我臺式電腦(8核16線程+16內存)的壓測性能對比數據.

主要使用goose壓測,具體可以參考項目中的子壓測工程 loadtest

性能壓測結果

模塊 場景 單節點qps 集羣qps 總結
配置中心 配置寫入,單機模式 1.5萬 1.5萬
配置中心 配置寫入,集羣模式 1.8千 1.5千 接入raft後沒有充分優化,待優化,理論上可接近單機模式
配置中心 配置查詢 8萬 n*8萬 集羣的查詢總qps是節點的倍數
註冊中心 服務實例註冊,http協議 1.2萬 1.0萬 註冊中心單機模式與集羣模式寫入的性能一致
註冊中心 服務實例註冊,grpc協議 1.2萬 1.2萬 grpc協議壓測工具沒有支持,目前沒有實際壓測,理論不會比http協議低
註冊中心 服務實例心跳,http協議 1.2萬 1.0萬 心跳是按實例計算和服務實例註冊一致共享qps
註冊中心 服務實例心跳,grpc協議 8萬以上 n*8萬 心跳是按請求鏈接計算,且不過註冊中心處理線程,每個節點只需管理當前節點的心跳,集羣總心跳qps是節點的倍數
註冊中心 查詢服務實例 3萬 n*3萬 集羣的查詢總qps是節點的倍數

注: 具體結果和壓測環境有關

壓測記錄

註冊中心查詢(單機3萬 qps):

配置中心查詢,兩個進程分別限流4萬qps同時壓測(共8萬qps),其中一個的壓測記錄:

容量分析

配置中心

  1. 配置中心的單機查詢8萬qps,很高,又支持水平擴容;集羣基本沒有查詢瓶頸。
  2. 配置中心所佔用的內存和配置內存有關,在內存沒有滿前,基本沒有瓶頸。
  3. 配置中心集羣寫入時統一在主節點寫入,寫入可能有瓶頸;目前1.5千tps,後面優化後應該能到1萬 tps以上。

註冊中心

  1. 註冊中心的單機查詢3萬qps,比較高,又支持水平擴容;集羣基本沒有查詢瓶頸。
  2. 註冊中心所佔用的內存和配置內存有關,在內存沒有滿前,基本沒有瓶頸。
  3. 註冊中心集羣寫入時每個節點都要寫一遍,整體集羣的寫入性能tps和單機理論上相當。
  4. http協議(v1.x版本)和grpc協議(v2.x)的心跳維護機制不同;http心跳是按實例計算和服務實例註冊一致共享qps, grpc的心跳是按請求鏈接計算且不過註冊中心處理線程。所有這類協議理論支持的容量差別很大。

註冊中心集羣註冊容量推理

  1. http協議註冊+心跳qps是1萬,每個實例5秒鐘一次心跳;理論上只能支持5萬服務實例左右。
  2. grpc協議,註冊qps假設也是1萬,心跳qps單實例8萬,3節點集羣總心跳24萬;如果平均一個應用實例1小時重連一次;支持註冊的服務實例總數爲:60*60*10000 = 3600萬,心跳支持的鏈接實例總數爲:5*24萬=120萬個鏈接實例(和集羣節點有關)。

結論:
如果使用v1.0x http協議,支持的實例在5萬個左右。
如果使用v2.0x grpc協議,理論上能到達千萬實例,基本沒有瓶頸。

4. rnacos 集羣部署

4.1 獲取rnacos應用包

方式1:從 github release 下載對應系統的應用包,解壓後即可運行。

linux 或 mac

# 解壓
tar -xvf rnacos-x86_64-apple-darwin.tar.gz
# 運行
./rnacos -e envfine

windows 解壓後直接運行 rnacos.exe 即可。

方式2: 通過docker 運行

#stable是最新正式版本號,也可以指定鏡像版本號,如: qingpan/rnacos:v0.3.0
docker pull qingpan/rnacos:stable  
# 在/path/rnacos/.env 配置文件中配置好運行參數
docker run --name mynacos -p 8848:8848 -p 9848:9848 -d -v /path/rnacos:/io qingpan/rnacos:stable

docker 的容器運行目錄是 /io,會從這個目錄讀寫配置文件

方式3:通過 cargo 編譯安裝

# 安裝
cargo install rnacos
# 運行
rnacos -e envfile

方式4: 下載源碼編譯運行

git clone https://github.com/heqingpan/rnacos.git
cd rnacos
cargo build --release
cargo run --release -- -e envfile

測試、試用推薦使用第1、第2種方式,直接下載就可以使用。

在linux下第1、第2種方式默認是musl版本(性能比gnu版本差一些),在生產服務對性能有要求的可以考慮使用第3、第4種在對應環境編譯gnu版本部署。

4.2 運行參數說明

同一個應用包需要支持不同場景,就需要支持設置自定義參數。

rnacos 運行參數支持通過環境變量,或指定配置文件方式設置。 如果不設置則按默認參數運行。

例子

# 從0.3.0版本開始支持 -e env_file 運行參數
./rnacos -e env_file

如果不指定文件時也會嘗試從當前目錄下.env文件加載配置參數

env_file內容的格式是

KEY1=VALUE1
KEY2=VALUE2
KEY3=VALUE3

運行參數:

參數KEY 內容描述 默認值 示例 開始支持的版本
RNACOS_HTTP_PORT rnacos監聽http端口 8848 8848 0.1.x
RNACOS_GRPC_PORT rnacos監聽grpc端口 默認是 HTTP端口+1000 9848 0.1.x
RNACOS_HTTP_WORKERS http工作線程數 cpu核數 8 0.1.x
RNACOS_CONFIG_DB_FILE 配置中心的本地數據庫文件地址【0.2.x後不在使用】 config.db config.db 0.1.x
RNACOS_CONFIG_DB_DIR 配置中心的本地數據庫sled文件夾, 會在系統運行時自動創建 nacos_db nacos_db 0.2.x
RNACOS_RAFT_NODE_ID 節點id 1 1 0.3.0
RNACOS_RAFT_NODE_ADDR 節點地址Ip:GrpcPort,單節點運行時每次啓動都會生效;多節點集羣部署時,只取加入集羣時配置的值 127.0.0.1:GrpcPort 127.0.0.1:9848 0.3.0
RNACOS_RAFT_AUTO_INIT 是否當做主節點初始化,(只在每一次啓動時生效) 節點1時默認爲true,節點非1時爲false true 0.3.0
RNACOS_RAFT_JOIN_ADDR 是否當做節點加入對應的主節點,LeaderIp:GrpcPort;只在第一次啓動時生效 127.0.0.1:9848 0.3.0
RUST_LOG 日誌等級:debug,info,warn,error;所有http,grpc請求都會打info日誌,如果不觀注可以設置爲error減少日誌量 info error 0.3.0

注:從v0.3.0開始,默認參數啓動的節點會被當做只有一個節點,當前節點是主節點的集羣部署。支持其它新增的從節點加入。

配置集羣規則

  1. 所有的集羣節點都需要設置RNACOS_RAFT_NODE_ID,RNACOS_RAFT_NODE_ADDR ,其中不同節點的node_id和 node_addr不能相同;node_id爲一個正整數,node_addr爲ip:grpc_port
  2. 集羣主節點: 初始設置RNACOS_RAFT_AUTO_INIT爲true (如果節點爲1,默認是 true,不用額外設置)。
  3. 集羣從節點: 初始設置RNACOS_RAFT_AUTO_INIT爲false (節點非1,默認就是false,不用額外設置);另外需要設置RNACOS_RAFT_JOIN_ADDR爲當前主節點的地址,以方便啓動時自動加入集羣中。
  4. 第2、3點只是爲了初始化組建集羣。集羣運行起來之後,後繼啓動配置從raft db中加載。
  5. 集羣節點數量不要求,可以是1、2、3、4、... ; 不過raft協議只支持小於集羣半數節點異常後繼續提供寫入服務(查詢不影響)。例如:3個節點集羣支持1個節點異常後提供寫入服務,2個節點集羣可以正常運行,不支持節點異常後提供服務。
  6. 從節點可以在使用過程中按需加入。比如原來3個節點,可能在使用一段時間後增加2個節點擴容。

4.3 集羣實例

按上面的配置規則,下面我們配置一個3節點集羣例子。

初始化節信息

  1. 主節點id爲1,地址爲127.0.0.1:9848
  2. 第一個從節點id爲2,地址爲127.0.0.1:9849
  3. 第二個從節點id爲3,地址爲127.0.0.1:9849

正式集羣部署的log等級建議設置爲warn,不打正常的請求日誌,只打報警或異常日誌,減少日誌量。

配置信息如下

env01

#file:env01 , Initialize with the leader node role
RUST_LOG=warn
RNACOS_HTTP_PORT=8848
RNACOS_RAFT_NODE_ADDR=127.0.0.1:9848
RNACOS_CONFIG_DB_DIR=db01
RNACOS_RAFT_NODE_ID=1
RNACOS_RAFT_AUTO_INIT=true

env02:

#file:env02 , Initialize with the follower node role
RUST_LOG=warn
RNACOS_HTTP_PORT=8849
RNACOS_RAFT_NODE_ADDR=127.0.0.1:9849
RNACOS_CONFIG_DB_DIR=db02
RNACOS_RAFT_NODE_ID=2
RNACOS_RAFT_JOIN_ADDR=127.0.0.1:9848

env03:

#file:env03 , Initialize with the follower node role
RUST_LOG=warn
RNACOS_HTTP_PORT=8850
RNACOS_RAFT_NODE_ADDR=127.0.0.1:9850
RNACOS_CONFIG_DB_DIR=db03
RNACOS_RAFT_NODE_ID=3
RNACOS_RAFT_JOIN_ADDR=127.0.0.1:9848

注: 上面的地址是本機運行多實例的地址,實際使用時換成具體的服務ip和port即可。

分別運行三個節點,需要先運行主節點成功後再運行

先運行主節點

nohup ./rnacos -e env01 > n01.log &

主節點功能啓動後,再運行從節點

nohup ./rnacos -e env02 > n02.log &
nohup ./rnacos -e env03 > n03.log &

實例過程中不同的節點需要在不同的服務器運行服務。

4.4 運行應用使用集羣

集羣服務啓動後,即可運行原有的 nacos 應用。

配置中心http api例子

echo "\npublish config t001:contentTest to node 1"
curl -X POST 'http://127.0.0.1:8848/nacos/v1/cs/configs' -d 'dataId=t001&group=foo&content=contentTest'
sleep 1

echo "\nget config info t001 from node 1, value:"
curl 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=t001&group=foo'

echo "\nget config info t001 from node 2, value:"
curl 'http://127.0.0.1:8849/nacos/v1/cs/configs?dataId=t001&group=foo'

echo "\nget config info t001 from node 3, value:"
curl 'http://127.0.0.1:8850/nacos/v1/cs/configs?dataId=t001&group=foo'
sleep 1

echo "\npublish config t002:contentTest02 to node 2"
curl -X POST 'http://127.0.0.1:8849/nacos/v1/cs/configs' -d 'dataId=t002&group=foo&content=contentTest02'
sleep 1

echo "\nget config info t002 from node 1, value:"
curl 'http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=t002&group=foo'

echo "\nget config info t002 from node 2, value:"
curl 'http://127.0.0.1:8849/nacos/v1/cs/configs?dataId=t002&group=foo'

echo "\nget config info t002 from node 3, value:"
curl 'http://127.0.0.1:8850/nacos/v1/cs/configs?dataId=t002&group=foo'

註冊中心http api例子

echo "\nregister instance nacos.test.001 to node 1"
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' -d 'port=8000&healthy=true&ip=192.168.1.11&weight=1.0&serviceName=nacos.test.001&groupName=foo&metadata={"app":"foo","id":"001"}'
echo "\nregister instance nacos.test.001 to node 2"
curl -X POST 'http://127.0.0.1:8849/nacos/v1/ns/instance' -d 'port=8000&healthy=true&ip=192.168.1.12&weight=1.0&serviceName=nacos.test.001&groupName=foo&metadata={"app":"foo","id":"002"}'
echo "\nregister instance nacos.test.001 to node 3"
curl -X POST 'http://127.0.0.1:8850/nacos/v1/ns/instance' -d 'port=8000&healthy=true&ip=192.168.1.13&weight=1.0&serviceName=nacos.test.001&groupName=foo&metadata={"app":"foo","id":"003"}'
sleep 1
echo "\n\nquery service instance nacos.test.001 from node 1, value:"
curl "http://127.0.0.1:8848/nacos/v1/ns/instance/list?&namespaceId=public&serviceName=foo%40%40nacos.test.001&groupName=foo&clusters=&healthyOnly=true"
echo "\n\nquery service instance nacos.test.001 from node 2, value:"
curl "http://127.0.0.1:8849/nacos/v1/ns/instance/list?&namespaceId=public&serviceName=foo%40%40nacos.test.001&groupName=foo&clusters=&healthyOnly=true"
echo "\n\nquery service instance nacos.test.001 from node 3, value:"
curl "http://127.0.0.1:8850/nacos/v1/ns/instance/list?&namespaceId=public&serviceName=foo%40%40nacos.test.001&groupName=foo&clusters=&healthyOnly=true"
echo "\n"

詳細使用說明參考rnacos book

5. 歡迎試用與共建

rnacos單機版本發佈已有4個月,期間有收到一些使用問題的反饋,目前主體功能已經算比較穩定,有使用nacos的同學歡迎試用。

項目已開源到 github gitee

使用過程中和什麼問題或建議可以到github提issues反饋。

如果對你有幫助就給個star鼓勵鼓勵 😃

對rnacos開發感興趣的同學也歡迎到github提rp共建。 rnacos發佈後已有一位同學參於共建,非常感謝一起共建的同學。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章