1. 程式人生 > >[從原始碼學設計]螞蟻金服SOFARegistry之服務上線

[從原始碼學設計]螞蟻金服SOFARegistry之服務上線

# [從原始碼學設計]螞蟻金服SOFARegistry之服務上線 [toc] ## 0x00 摘要 SOFARegistry 是螞蟻金服開源的一個生產級、高時效、高可用的服務註冊中心。 本系列文章重點在於分析設計和架構,即利用多篇文章,從多個角度反推總結 DataServer 或者 SOFARegistry 的實現機制和架構思路,讓大家藉以學習阿里如何設計。 本文為第十三篇,介紹從SessionServer角度看的服務上線。 本文以介紹業務為主,順便整理邏輯,設計和模式。因為註冊過程牽扯模組太多,所以本文僅僅專注在註冊過程中Session Server的部分。 ## 0x01 業務領域 ### 1.1 應用場景 服務的上下線過程是指服務通過程式碼呼叫執行常規註冊(Publisher#register) 和下線(Publisher#unregister)操作,不考慮因為服務宕機等意外情況導致的下線場景。 #### 1.1.1 服務釋出 一個典型的 “RPC 呼叫的服務定址” 應用場景,服務的提供方通過如下兩個步驟完成服務釋出: 1. 註冊,將自己以 Publisher 的角色註冊到 SOFARegistry; 2. 釋出,將需要釋出的資料 (通常是IP 地址、埠、呼叫方式等) 釋出到 SOFARegistry; 與此相對應的,服務的呼叫方通過如下步驟實現服務呼叫: 1. 註冊,將自己以 Subscriber 的角色註冊到 SOFARegistry; 2. 訂閱,收到 SOFARegistry 推送的服務資料; #### 1.1.2 SessionServer的必要性 在SOFARegistry中,所有 Client 在註冊和訂閱資料時,根據 dataInfoId 做一致性 Hash,計算出應該訪問哪一臺 DataServer,然後與該 DataServer 建立長連線。 由於每個 Client 通常都會註冊和訂閱比較多的 dataInfoId 資料,因此我們可以預見每個 Client 均會與好幾臺 DataServer 建立連線。這個架構存在的問題是:“每臺 DataServer 承載的連線數會隨 Client 數量的增長而增長,每臺 Client 極端的情況下需要與每臺 DataServer 都建連,因此通過 DataServer 的擴容並不能線性的分攤 Client 連線數”。 所以,為資料分片層(DataServer)專門設計一個連線代理層是非常重要的,所以 SOFARegistry 就有了 SessionServer 這一層。隨著 Client 數量的增長,可以通過擴容 SessionServer 就解決了單機的連線數瓶頸問題。 ### 1.2 問題點 因為SessionServer是一箇中間層,所以看起來好像比較簡單,表面上看,就是接受,轉發。 但是實際上,在大型系統中,應該如何在邏輯上,物理上實現模組分割,解耦都是非常有必要的。 ### 1.3 阿里方案 我們主要看看阿里方案的註冊部分。 #### 1.3.1 註冊過程 ![一次服務的上線(註冊)過程](https://img2020.cnblogs.com/blog/1850883/202101/1850883-20210105195621150-591781910.png) 服務的上下線過程,是指服務通過程式碼呼叫做正常的註冊(publisher.register) 和 下線(publisher.unregister),不考慮因為服務宕機等意外情況導致的下線。如上圖,大概呈現了“一次服務註冊過程”的服務資料在內部流轉過程。 1. Client 呼叫 publisher.register 向 SessionServer 註冊服務。 2. SessionServer 收到服務資料 (PublisherRegister) 後,將其寫入記憶體 (SessionServer 會儲存 Client 的資料到記憶體,用於後續可以跟 DataServer 做定期檢查),再根據 dataInfoId 的一致性 Hash 尋找對應的 DataServer,將 PublisherRegister 發給 DataServer。 3. DataServer 接收到 PublisherRegister 資料,首先也是將資料寫入記憶體 ,DataServer 會以 dataInfoId 的維度彙總所有 PublisherRegister。同時,DataServer 將該 dataInfoId 的變更事件通知給所有 SessionServer,變更事件的內容是 dataInfoId 和版本號資訊 version。 4. 同時,非同步地,DataServer 以 dataInfoId 維度增量地同步資料給其他副本。因為 DataServer 在一致性 Hash 分片的基礎上,對每個分片儲存了多個副本(預設是3個副本)。 5. SessionServer 接收到變更事件通知後,對比 SessionServer 記憶體中儲存的 dataInfoId 的 version,若發現比 DataServer 發過來的小,則主動向 DataServer 獲取 dataInfoId 的完整資料,即包含了所有該 dataInfoId 具體的 PublisherRegister 列表。 6. 最後,SessionServer 將資料推送給相應的 Client,Client 就接收到這一次服務註冊之後的最新的服務列表資料。 #### 1.3.2 圖示 下圖展示了 Publisher 註冊的程式碼流轉過程 這個過程也是採用了 Handler - Task & Strategy - Listener 的方式來處理,任務在程式碼內部的處理流程和訂閱過程基本一致。 ![圖 - 程式碼流轉:Publisher 註冊](https://img2020.cnblogs.com/blog/1850883/202101/1850883-20210105195653301-763948519.png) ## 0x02 Client SDK PublisherRegistration 是Client的介面,釋出資料的關鍵程式碼如下: ```java // 構造釋出者登錄檔 PublisherRegistration registration = new PublisherRegistration("com.alipay.test.demo.service:1.0@DEFAULT"); registration.setGroup("TEST_GROUP"); registration.setAppName("TEST_APP"); // 將登錄檔註冊進客戶端併發布資料 Publisher publisher = registryClient.register(registration, "10.10.1.1:12200?xx=yy"); // 如需覆蓋上次釋出的資料可以使用釋出者模型重新發布資料 publisher.republish("10.10.1.1:12200?xx=zz"); ``` 釋出資料的關鍵是構造 PublisherRegistration,該類包含三個屬性: | 屬性名 | 屬性型別 | 描述 | | ------- | -------- | ------------------------------------------------------------ | | dataId | String | 資料ID,釋出訂閱時需要使用相同值,資料唯一標識由 dataId + group + instanceId 組成。 | | group | String | 資料分組,釋出訂閱時需要使用相同值,資料唯一標識由 dataId + group + instanceId 組成,預設值 DEFAULT_GROUP。 | | appName | String | 應用 appName。 | ## 0x03 Session server 流程來到了Session server。 ### 3.1 Bean 首先,可以通過Beans來入手。 ```java @Bean(name = "serverHandlers") public Co