1. 程式人生 > >[原始碼閱讀] 阿里SOFA服務註冊中心MetaServer(1)

[原始碼閱讀] 阿里SOFA服務註冊中心MetaServer(1)

# [原始碼閱讀] 阿里SOFA服務註冊中心MetaServer(1) [toc] ## 0x00 摘要 SOFARegistry 是螞蟻金服開源的一個生產級、高時效、高可用的服務註冊中心。本系列將帶領大家一起分析其MetaServer的實現機制,本文為第一篇,介紹MetaServer總體架構。 本系列總體參考了官方的部落格,具體請參見"0xFF 參考"。大家可以把參考作為總綱,我這系列文章作為註釋補遺翻閱。 ## 0x01 服務註冊中心 ### 1.1 服務註冊中心簡介 在微服務架構下,一個網際網路應用的服務端背後往往存在大量服務間的相互呼叫。例如服務 A 在鏈路上依賴於服務 B,那麼在業務發生時,服務 A 需要知道服務 B 的地址,才能完成服務呼叫。而分散式架構下,每個服務往往都是叢集部署的,叢集中的機器也是經常變化的,所以服務 B 的地址不是固定不變的。如果要保證業務的可靠性,服務呼叫者則需要感知被呼叫服務的地址變化。 既然成千上萬的服務呼叫者都要感知這樣的變化,**那這種感知能力便下沉成為微服務中一種固定的架構模式:服務註冊中心**。 服務註冊中心裡,有服務提供者和服務消費者兩種重要的角色,服務呼叫方是消費者,服務被調方是提供者。對於同一臺機器,往往兼具兩者角色,既被其它服務呼叫,也呼叫其它服務。服務提供者將自身提供的服務資訊釋出到服務註冊中心,服務消費者通過訂閱的方式感知所依賴服務的資訊是否發生變化。 服務註冊中心在服務呼叫的場景中,扮演一個“中介”的角色,服務釋出者 (Publisher) 將服務釋出到服務註冊中心,服務呼叫方 (Subscriber) 通過訪問服務註冊中心就能夠獲取到服務資訊,進而實現呼叫。 Subscriber 在第一次呼叫服務時,會通過 Registry 找到相應的服務的 IP 地址列表,通過負載均衡演算法從 IP 列表中取一個服務提供者的伺服器呼叫服務。同時 Subscriber 會將 Publisher 的服務列表資料快取到本地,供後續使用。當 Subscriber 後續再呼叫 Publisher 時,優先使用快取的地址列表,不需要再去請求Registry。 ```java +----------+ +---------+ |Subscriber| <--------+ +--------+ |Publisher| +----------+ | | +---------+ | | +----------+ | v |Subscriber| <--------+ +----------++ +---------+ +----------+ <---+ Registry | <-------+Publisher| | +----------++ +---------+ +----------+ | ^ |Subscriber| <--------+ | +----------+ | | +---------+ +----------+Publisher| +---------+ ``` 服務註冊中心Registry的最主要能力是服務註冊和服務發現兩個過程。 - 服務註冊的過程最重要是對服務釋出的資訊進行儲存。 - 服務發現的過程是把服務釋出端的所有變化(包括節點變化和服務資訊變化)及時準確的通知到訂閱方的過程。 ### 1.2 SOFARegistry 總體架構 #### 1.2.1 分層 SOFARegistry 作為服務註冊中心,分為4個層,分別為: - **Client 層** Client 層是應用伺服器叢集。Client 層是應用層,每個應用系統通過依賴註冊中心相關的客戶端 jar 包,通過程式設計方式來使用服務註冊中心的服務釋出和服務訂閱能力。 - **Session 層** Session層是伺服器叢集。顧名思義,Session 層是會話層,通過長連線和 Client 層的應用伺服器保持通訊,負責接收 Client 的服務釋出和服務訂閱請求。 在服務註冊中心的服務端因為每個儲存節點對應的客戶端的連結資料量有限,必須進行特殊的一層劃分用於專門收斂無限擴充的客戶端連線,然後在透傳相應的請求到儲存層。 該層只在記憶體中儲存各個服務的釋出訂閱關係,對於具體的服務資訊,只在 Client 層和 Data 層之間透傳轉發。Session 層是一個無資料狀態的代理層,可以隨著 Client 層應用規模的增長而擴容
。 因為 SOFARegistry 的服務發現需要較高的時效性,對外表現為主動推送變更到客戶端,所以推送的主體實現也集中在 Session 層,內部的推拉結合主要是通過 Data 儲存層的資料版本變更推送到所有 Session 節點,各個 Session 節點根據儲存的訂閱關係和首次訂閱獲取的資料版本資訊進行比對,最終確定推送給那些服務消費方客戶端。 - **Data 層** 資料伺服器叢集。Data 層通過分片儲存的方式儲存著所用應用的服務註冊資料。資料按照 dataInfoId(每一份服務資料的唯一標識)進行一致性 Hash 分片,多副本備份,保證資料的高可用。Data 層可以隨著資料規模的增長,在不影響業務的前提下實現平滑的擴縮容
。 - **Meta 層** 元資料伺服器叢集。這個叢集管轄的範圍是 Session 伺服器叢集和 Data 伺服器叢集的伺服器資訊,其角色就相當於 SOFARegistry 架構內部的服務註冊中心,只不過 SOFARegistry 作為服務註冊中心是服務於廣大應用服務層,而 Meta 叢集是服務於 SOFARegistry 內部的 Session 叢集和 Data 叢集,Meta 層能夠感知到 Session 節點和 Data 節點的變化,並通知叢集的其它節點。 ### 1.3 為什麼要分層 SOFARegistry 內部為什麼要進行資料分層,是因為系統容量的限制。 在 SOFARegistry 的應用場景中,體量龐大的資料主要有兩類:Session 資料、服務資訊資料。兩類資料的相同之處在於其資料量都會不斷擴充套件,而不同的是其擴充套件的原因並不相同: - Session 是對應於 Client 的連線,其資料量是隨著業務機器規模的擴充套件而增長, - 服務資訊資料量的增長是由 Publisher 的釋出所決定。 所以 SOFARegistry 通過分層設計,將兩種資料隔離,從而使二者的擴容互不影響。 ![圖3 - 分層,擴容互不影響](https://cdn.nlark.com/yuque/0/2019/png/307286/1571043263429-9b159551-1c94-4a4f-afbf-199b556cb401.png) 這也是 SOFARegistry 設計三層模型的原因,通過 SessionServer 來負責與 Client 的連線,將每個 Client 的連線數收斂到 1,這樣當 Client 數量增長時,只需要擴容 SessionServer 叢集就可以了。 所以從設計初衷上我們就能夠看出來 SessionServer 必須要滿足的兩個主要能力:從 DataServer 獲取服務資訊資料;以及儲存與 Client 的會話。 ## 0x02 MetaServer ### 2.1簡介 MetaServer 在 SOFARegistry 中,承擔著叢集元資料管理的角色,用來維護叢集成員列表
,可以認為是 SOFARegistry 註冊中心的註冊中心。 MetaServer 作為 SOFARegistry 的元資料中心,其核心功能可以概括為:叢集成員列表管理。比如: - 節點列表的註冊與儲存 - 節點列表的變更通知 - 節點健康監測 當 SessionServer 和 DataServer 需要知道叢集列表,並且需要擴縮容時,MetaServer 將會提供相應的資料。 其內部架構如下圖所示: ![內部架構圖](https://cdn.nlark.com/yuque/0/2019/png/338467/1568254931660-96379e5d-2ed0-472d-affa-edfb99c6bf24.png) MetaServer 基於 Bolt, 通過 TCP 私有協議的形式對外提供服務,包括 DataServer, SessionServer 等,處理節點的註冊,續約和列表查詢等請求。 同時也基於 Http 協議提供控制介面,比如可以控制 session 節點是否開啟變更通知, 健康檢查介面等。 成員列表資料儲存在 Repository 中,Repository 被一致性協議層進行包裝,作為 SOFAJRaft 的狀態機實現,所有對 Repository 的操作都會同步到其他節點, 通過Rgistry來操作儲存層。 MetaServer 使用 Raft 協議保證高可用和資料一致性, 同時也會保持與註冊的節點的心跳,對於心跳超時沒有續約的節點進行驅逐,來保證資料的有效性。 在可用性方面,只要未超過半數節點掛掉,叢集都可以正常對外提供服務,半數以上掛掉,Raft 協議無法選主和日誌複製,因此無法保證註冊的成員資料的一致性和有效性。整個叢集不可用 不會影響 Data 和 Session 節點的正常功能,只是無法感知節點列表變化。 ### 2.2 問題 空談無用,just show the code。於是讓我們帶著問題來思考,即從巨集觀和微觀角度來思考MetaServer應該實現什麼功能,具體是怎麼實現的。 思考: - MetaServer具體是以什麼方式實現。 - MetaServer如何實現高可用。 - MetaServer如何實現或者應用內部通訊協議。 - MetaServer如何保持DataNode列表和SessionNode列表。 - MetaServer如何處理節點列表變更推送。 - MetaServer如何處理節點列表查詢。 - MetaServer如何處理MetaServer如何保證資料一致性。 - 各個叢集是如何搭建,如何完成高可用。 下面我們就一一分析。 ## 0x03 程式碼結構 我們在 `sofa-registry-5.4.2/server/server/meta/src/main/java/com/alipay/sofa/registry/server/meta` 看看目錄和檔案結構。 按照目錄我們可以大致瞭解功能 - MetaApplication.java :MetaServer程式主體,入口。 - bootstrap :負責MetaServer的啟動和配置。 - executor :負責各種定時管理任務,他的啟動設定是在 MetaServerBootstrap.initRaft 之中。 - listener :SOFARegistry 採用了 Handler - Task & Strategy - Listener 的方式來應對服務註冊中的各種場景和任務,這樣的處理模型能夠儘可能的讓程式碼和架構清晰整潔。 - node :對業務節點的抽象,包括DataNode,SessionNode,MetaNode。 - registry :通過Registry來操作儲存層,所有對 Repository 的操作都會同步到其他節點。 - remoting :對外互動介面,提供各種對外的 handler。 - repository :叢集節點列表儲存在 Repository 中,通過 Raft 強一致性協議對外提供節點註冊、續約、列表查詢等 Bolt 請求,從而保障叢集獲得的資料是強一致性的。Repository 被一致性協議層進行包裝,作為 SOFAJRaft 的狀態機實現。 - resource :http Server的介面,用來響應控制訊息。 - store :封裝了儲存節點的操作。 - task :封裝了非同步執行邏輯,通過TaskDispatcher,TaskExecutors 來執行各種定義好的非同步Task。 具體程式碼結構如下: ```java . ├── MetaApplication.java ├── bootstrap │   ├── AbstractNodeConfigBean.java │   ├── EnableMetaServer.java │   ├── MetaServerBootstrap.java │   ├── MetaServerConfig.java │   ├── MetaServerConfigBean.java │   ├── MetaServerConfiguration.java │   ├── MetaServerInitializerConfiguration.java │   ├── NodeConfig.java │   ├── NodeConfigBeanProperty.java │   └── ServiceFactory.java ├── executor │   └── ExecutorManager.java ├── listener │   ├── DataNodeChangePushTaskListener.java │   ├── PersistenceDataChangeNotifyTaskListener.java │   ├── ReceiveStatusConfirmNotifyTaskListener.java │   └── SessionNodeChangePushTaskListener.java ├── node │   ├── DataNodeService.java │   ├── MetaNodeService.java │   ├── NodeOperator.java │   ├── NodeService.java │   ├── SessionNodeService.java │   └── impl │   ├── DataNodeServiceImpl.java │   ├── MetaNodeServiceImpl.java │   └── SessionNodeServiceImpl.java ├── registry │   ├── MetaServerRegistry.java │   └── Registry.java ├── remoting │   ├── DataNodeExchanger.java │   ├── MetaClientExchanger.java │   ├── MetaServerExchanger.java │   ├── RaftExchanger.java │   ├── SessionNodeExchanger.java │   ├── connection │   │   ├── DataConnectionHandler.java │   │   ├── MetaConnectionHandler.java │   │   ├── NodeConnectManager.java │   │   └── SessionConnectionHandler.java │   └── handler │   ├── AbstractServerHandler.java │   ├── DataNodeHandler.java │   ├── FetchProvideDataRequestHandler.java │   ├── GetNodesRequestHandler.java │   ├── RenewNodesRequestHandler.java │   └── SessionNodeHandler.java ├── repository │   ├── NodeConfirmStatusService.java │   ├── NodeRepository.java │   ├── RepositoryService.java │   ├── VersionRepositoryService.java │   ├── annotation │   │   └── RaftAnnotationBeanPostProcessor.java │   └── service │   ├── DataConfirmStatusService.java │   ├── DataRepositoryService.java │   ├── MetaRepositoryService.java │   ├── SessionConfirmStatusService.java │   ├── SessionRepositoryService.java │   └── SessionVersionRepositoryService.java ├── resource │   ├── BlacklistDataResource.java │   ├── DecisionModeResource.java │   ├── HealthResource.java │   ├── MetaDigestResource.java │   ├── MetaStoreResource.java │   ├── PersistentDataResource.java │   ├── RenewSwitchResource.java │   └── StopPushDataResource.java ├── store │   ├── DataStoreService.java │   ├── MetaStoreService.java │   ├── RenewDecorate.java │   ├── SessionStoreService.java │   └── StoreService.java └── task ├── AbstractMetaServerTask.java ├── Constant.java ├── DataNodeChangePushTask.java ├── MetaServerTask.java ├── PersistenceDataChangeNotifyTask.java ├── ReceiveStatusConfirmNotifyTask.java ├── SessionNodeChangePushTask.java └── processor ├── DataNodeSingleTaskProcessor.java ├── MetaNodeSingleTaskProcessor.java └── SessionNodeSingleTaskProcessor.java 16 directories, 75 files ``` ## 0x04 啟動執行 啟動可以參考 https://www.sofastack.tech/projects/sofa-registry/server-quick-start/ SOFARegistry 支援兩種部署模式,分別是整合部署模式及獨立部署模式。 ### 4.1 整合部署 #### 4.1.1 Linux/Unix/Mac 啟動命令:`sh bin/startup.sh` #### 4.1.2 Windows 雙擊 bin 目錄下的 startup.bat 執行檔案。 #### 4.1.3 啟動資訊 通過下列log我們可以看到啟動資訊。 MetaApplication ```java [2020-09-12 20:23:05,463][INFO][main][MetaServerBootstrap] - Open meta server port 9612 success! [2020-09-12 20:23:08,198][INFO][main][MetaServerBootstrap] - Open http server port 9615 success! [2020-09-12 20:23:10,298][INFO][main][MetaServerBootstrap] - Raft server port 9614 start success!group RegistryGroup [2020-09-12 20:23:10,322][INFO][main][MetaServerInitializerConfiguration] - Started MetaServer ``` DataApplication ```java [2020-09-12 20:23:25,004][INFO][main][DataServerBootstrap] - Open http server port 9622 success! [2020-09-12 20:23:26,084][INFO][main][DataServerBootstrap] - start server success [2020-09-12 20:23:26,094][INFO][main][DataApplication] - Started DataApplication in 10.217 seconds (JVM running for 11.316) ``` SessionApplication ```java [2020-09-12 20:23:50,243][INFO][main][SessionServerBootstrap] - Open http server port 9603 success! [2020-09-12 20:23:50,464][INFO][main][SessionServerInitializer] - Started SessionServer [2020-09-12 20:23:50,526][INFO][main][SessionApplication] - Started SessionApplication in 12.516 seconds (JVM running for 13.783) ``` 各個 Server 的預設埠分別為: ```java meta.server.sessionServerPort=9610 meta.server.dataServerPort=9611 meta.server.metaServerPort=9612 meta.server.raftServerPort=9614 meta.server.httpServerPort=9615 ``` 可訪問三個角色提供的健康監測 API,或檢視日誌 logs/registry-startup.log: ```bash # 檢視meta角色的健康檢測介面: $ curl http://localhost:9615/health/check {"success":true,"message":"... raftStatus:Leader"} # 檢視data角色的健康檢測介面: $ curl http://localhost:9622/health/check {"success":true,"message":"... status:WORKING"} # 檢視session角色的健康檢測介面: $ curl http://localhost:9603/health/check {"success":true,"message":"..."} ``` ### 4.2 獨立部署 在這裡我們可以看出來各種叢集是如何搭建,以及如何在叢集內部通訊,分散式協調。 按照常理來說,每個叢集都應該依賴zookeeper之類的軟體來進行自己內部的協調,比如統一命名服務、狀態同步服務、叢集管理、分散式應用配置項。但實際上我們沒有發現類似的使用。 具體看配置檔案發現,每臺機器都要設定所有的metaServer的host。這說明Data Server, Session Server則強依賴Meta Server。 實際上,MetaServer 使用 Raft 協議保證高可用和資料一致性, 同時也會保持與註冊的節點的心跳,對於心跳超時沒有續約的節點進行驅逐,來保證資料的有效性。Meta 層能夠感知到 Session 節點和 Data 節點的變化,並通知叢集的其它節點。 這就涉及到各個角色的 failover 機制: - MetaServer 叢集部署,內部基於 Raft 協議選舉和複製,只要不超過 1⁄2 節點宕機,就可以對外服務。 - DataServer 叢集部署,基於一致性 Hash 承擔不同的資料分片,資料分片擁有多個副本,一個主副本和多個備副本。如果 DataServer 宕機,MetaServer 能感知,並通知所有 DataServer 和 SessionServer,資料分片可 failover 到其他副本,同時 DataServer 叢集內部會進行分片資料的遷移。 - SessionServer 叢集部署,任何一臺 SessionServer 宕機時 Client 會自動 failover 到其他 SessionServer,並且 Client 會拿到最新的 SessionServer 列表,後續不會再連線這臺宕機的 SessionServer。 #### 4.2.1 部署meta 每臺機器在部署時需要修改 *conf/application.properties* 配置: ```bash # 將3臺meta機器的ip或hostname配置到下方(填入的hostname會被內部解析為ip地址) nodes.metaNode=DefaultDat