[源碼閱讀] 阿里SOFA服務註冊中心MetaServer(1)
- 2020 年 10 月 8 日
- 筆記
- 005_源碼分析, 008_微服務, 010_業界方案, 210_SOFAStack
[源碼閱讀] 阿里SOFA服務註冊中心MetaServer(1)
0x00 摘要
SOFARegistry 是螞蟻金服開源的一個生產級、高時效、高可用的服務註冊中心。本系列將帶領大家一起分析其MetaServer的實現機制,本文為第一篇,介紹MetaServer總體架構。
本系列總體參考了官方的部落格,具體請參見”0xFF 參考”。大家可以把參考作為總綱,我這系列文章作為注釋補遺翻閱。
0x01 服務註冊中心
1.1 服務註冊中心簡介
在微服務架構下,一個互聯網應用的服務端背後往往存在大量服務間的相互調用。例如服務 A 在鏈路上依賴於服務 B,那麼在業務發生時,服務 A 需要知道服務 B 的地址,才能完成服務調用。而分散式架構下,每個服務往往都是集群部署的,集群中的機器也是經常變化的,所以服務 B 的地址不是固定不變的。如果要保證業務的可靠性,服務調用者則需要感知被調用服務的地址變化。
既然成千上萬的服務調用者都要感知這樣的變化,那這種感知能力便下沉成為微服務中一種固定的架構模式:服務註冊中心。
服務註冊中心裡,有服務提供者和服務消費者兩種重要的角色,服務調用方是消費者,服務被調方是提供者。對於同一台機器,往往兼具兩者角色,既被其它服務調用,也調用其它服務。服務提供者將自身提供的服務資訊發布到服務註冊中心,服務消費者通過訂閱的方式感知所依賴服務的資訊是否發生變化。
服務註冊中心在服務調用的場景中,扮演一個「中介」的角色,服務發布者 (Publisher) 將服務發布到服務註冊中心,服務調用方 (Subscriber) 通過訪問服務註冊中心就能夠獲取到服務資訊,進而實現調用。
Subscriber 在第一次調用服務時,會通過 Registry 找到相應的服務的 IP 地址列表,通過負載均衡演算法從 IP 列表中取一個服務提供者的伺服器調用服務。同時 Subscriber 會將 Publisher 的服務列表數據快取到本地,供後續使用。當 Subscriber 後續再調用 Publisher 時,優先使用快取的地址列表,不需要再去請求Registry。
+----------+ +---------+
|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 通過分層設計,將兩種數據隔離,從而使二者的擴容互不影響。
這也是 SOFARegistry 設計三層模型的原因,通過 SessionServer 來負責與 Client 的連接,將每個 Client 的連接數收斂到 1,這樣當 Client 數量增長時,只需要擴容 SessionServer 集群就可以了。 所以從設計初衷上我們就能夠看出來 SessionServer 必須要滿足的兩個主要能力:從 DataServer 獲取服務資訊數據;以及保存與 Client 的會話。
0x02 MetaServer
2.1簡介
MetaServer 在 SOFARegistry 中,承擔著集群元數據管理的角色,用來維護集群成員列表,可以認為是 SOFARegistry 註冊中心的註冊中心。
MetaServer 作為 SOFARegistry 的元數據中心,其核心功能可以概括為:集群成員列表管理。比如:
- 節點列表的註冊與存儲
- 節點列表的變更通知
- 節點健康監測
當 SessionServer 和 DataServer 需要知道集群列表,並且需要擴縮容時,MetaServer 將會提供相應的數據。
其內部架構如下圖所示:
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。
具體程式碼結構如下:
.
├── 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 啟動運行
啟動可以參考 //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
[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
[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
[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 的默認埠分別為:
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:
# 查看meta角色的健康檢測介面:
$ curl //localhost:9615/health/check
{"success":true,"message":"... raftStatus:Leader"}
# 查看data角色的健康檢測介面:
$ curl //localhost:9622/health/check
{"success":true,"message":"... status:WORKING"}
# 查看session角色的健康檢測介面:
$ curl //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 配置:
# 將3台meta機器的ip或hostname配置到下方(填入的hostname會被內部解析為ip地址)
nodes.metaNode=DefaultDataCenter:<meta_hostname1>,<meta_hostname2>,<meta_hostname3>
nodes.localDataCenter=DefaultDataCenter
4.2.2 部署data
每台機器在部署時需要修改 conf/application.properties 配置:
# 將3台 meta 機器的 ip 或 hostname 配置到下方(填入的 hostname 會被內部解析為 ip 地址)
nodes.metaNode=DefaultDataCenter:<meta_hostname1>,<meta_hostname2>,<meta_hostname3>
nodes.localDataCenter=DefaultDataCenter
data.server.numberOfReplicas=1000
4.2.3 部署 session
每台機器在部署時需要修改 conf/application.properties 配置:
# 將3台 meta 機器的 ip 或 hostname 配置到下方(填入的 hostname 會被內部解析為 ip 地址)
nodes.metaNode=DefaultDataCenter:<meta_hostname1>,<meta_hostname2>,<meta_hostname3>
nodes.localDataCenter=DefaultDataCenter
nodes.localRegion=DefaultZone
0x05 總體邏輯
MetaServer 在啟動時,會啟動三個 Bolt Server,並且註冊 Processor Handler,處理對應的請求:
- DataServer:處理 DataNode 相關的請求;
- SessionServer:處理 SessionNode 相關的請求;
- MetaServer:處理MetaNode相關的請求;
然後啟動 HttpServer, 用於處理 Admin 請求,提供推送開關,集群數據查詢等 Http 介面。
最後啟動 Raft 服務, 每個節點同時作為 RaftClient 和 RaftServer, 用於集群間的變更和數據同步。
5.1 程式主體
MetaServer 是一個SpringBootApplication,主要起作用的就是EnableMetaServer。
@EnableMetaServer
@SpringBootApplication
public class MetaApplication {
public static void main(String[] args) {
SpringApplication.run(MetaApplication.class, args);
}
}
具體參見下圖
+-------------------+
| @EnableMetaServer |
| |
| MetaApplication |
+-------------------+
EnableMetaServer註解引入了MetaServerConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MetaServerConfiguration.class)
public @interface EnableMetaServer {
}
MetaServerConfiguration是各種配置,並且引入 MetaServerInitializerConfiguration負責啟動。
@Configuration
@Import(MetaServerInitializerConfiguration.class)
@EnableConfigurationProperties
public class MetaServerConfiguration {
}
所以我們可以知道:
- MetaServerConfiguration :負責配置
- MetaServerInitializerConfiguration : 負責啟動
於是程式總結結構演化為下圖:
(Init)
+------------------------------------+
| MetaServerInitializerConfiguration |
+-------------+----------------------+
^
+-------------------+ |
| @EnableMetaServer | |
| | |
| MetaApplication | |
+-------------+-----+ | (Configuration)
| +---------+---------------+
+--------------> | MetaServerConfiguration |
+-------------------------+
下面我們開始引入配置。
5.2 配置
MetaServer 模組的各個 bean 在 JavaConfig 中統一配置,JavaConfig 類為 MetaServerConfiguration。
5.2.1 配置分類
MetaServerConfiguration 具體有以下幾類配置:
- Bootstrap,負責MetaServer啟動配置,是核心啟動類。
- ServerConfig,負責MetaServer的配置項,比如MetaServerConfig,NodeConfig,PropertySplitter。
- ServerServiceConfiguration,負責服務相關的配置,比如 sessionNodeService,storeServiceFactory,sessionStoreService。
- ServerRepositoryConfiguration,負責Repository相關的配置,比如dataRepositoryService,sessionRepositoryService等。
- ServerRemotingConfiguration,負責網路相關配置,比如BoltExchange,JerseyExchange,這裡隨後會重點說明。
- ResourceConfiguration,負責Resource相關配置,比如 jerseyResourceConfig,persistentDataResource。
- ServerTaskConfiguration,負責各種 task 相關配置 ,比如dataNodeSingleTaskProcessor。
- ExecutorConfiguation,ExecutorManager相關配置。
- MetaDBConfiguration,DB相關配置。
具體縮略版程式碼如下 :
@Configuration
@Import(MetaServerInitializerConfiguration.class)
@EnableConfigurationProperties
public class MetaServerConfiguration {
@Bean
@ConditionalOnMissingBean
public MetaServerBootstrap metaServerBootstrap() {
}
@Configuration
protected static class MetaServerConfigBeanConfiguration {
}
@Configuration
public static class MetaServerServiceConfiguration {
}
@Configuration
public static class MetaServerRepositoryConfiguration {
}
@Configuration
public static class MetaServerRemotingConfiguration {
}
@Configuration
public static class ResourceConfiguration {
}
@Configuration
public static class MetaServerTaskConfiguration {
}
@Configuration
public static class ExecutorConfiguation {
}
@Configuration
public static class MetaDBConfiguration {
}
}
我們的圖進化如下:
(Init)
+------------------------------------+
| MetaServerInitializerConfiguration |
+-------------+----------------------+ +---------------------+
^ +--> | MetaServerBootstrap |
+-------------------+ | | +---------------------+
| @EnableMetaServer | | | +---------------------------------+
| | | +--> |MetaServerConfigBeanConfiguration|
| MetaApplication | | | +---------------------------------+
+--------------+----+ | | +------------------------------+
| | +--> |MetaServerServiceConfiguration|
| | | +------------------------------+
| | | +---------------------------------+
| | +--> |MetaServerRepositoryConfiguration|
| | | +---------------------------------+
| | (Configuration) | +-------------------------------+
| +---------+---------------+ +--> |MetaServerRemotingConfiguration|
+--------------> | MetaServerConfiguration | +-----+ +-------------------------------+
+-------------------------+ | +----------------------+
+--> |ResourceConfiguration |
| +----------------------+
| +---------------------------+
+--> |MetaServerTaskConfiguration|
| +---------------------------+
| +---------------------+
+--> |ExecutorConfiguation |
| +---------------------+
| +--------------------+
+--> |MetaDBConfiguration |
+--------------------+
下圖為了手機閱讀
5.2.2 handler的配置
這裡要特殊提一下handler的配置,因為這是後續分析的主體之一,是三個 Bolt Server的handler。
@Configuration
public static class MetaServerRemotingConfiguration {
@Bean
public Exchange boltExchange() {
return new BoltExchange();
}
@Bean
public Exchange jerseyExchange() {
return new JerseyExchange();
}
@Bean(name = "sessionServerHandlers")
public Collection<AbstractServerHandler> sessionServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(sessionConnectionHandler());
list.add(sessionNodeHandler());
list.add(renewNodesRequestHandler());
list.add(getNodesRequestHandler());
list.add(fetchProvideDataRequestHandler());
return list;
}
@Bean(name = "dataServerHandlers")
public Collection<AbstractServerHandler> dataServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(dataConnectionHandler());
list.add(getNodesRequestHandler());
list.add(dataNodeHandler());
list.add(renewNodesRequestHandler());
list.add(fetchProvideDataRequestHandler());
return list;
}
@Bean(name = "metaServerHandlers")
public Collection<AbstractServerHandler> metaServerHandlers() {
Collection<AbstractServerHandler> list = new ArrayList<>();
list.add(metaConnectionHandler());
list.add(getNodesRequestHandler());
return list;
}
}
於是我們的總體架構進化具體見下圖
(Init)
+------------------------------------+
| MetaServerInitializerConfiguration |
+--------------+---------------------+
^
|
|
|
|
| +---------------------+
| +--> | MetaServerBootstrap |
+-------------------+ | | +---------------------+
| @EnableMetaServer | | | +---------------------------------+
| | | +--> |MetaServerConfigBeanConfiguration|
| MetaApplication | | | +---------------------------------+
+--------------+----+ | | +------------------------------+ +-----------------------+
| | +--> |MetaServerServiceConfiguration| +---> | sessionServerHandlers |
| | | +------------------------------+ | +-----------------------+
| | | +---------------------------------+ | +--------------------+
| | +--> |MetaServerRepositoryConfiguration+-------> | dataServerHandlers |
| | | +---------------------------------+ | +--------------------+
| | (Configuration) | +-------------------------------+ | +--------------------+
| +---------+---------------+ +--> |MetaServerRemotingConfiguration| +---> | metaServerHandlers |
+-----------> | MetaServerConfiguration | +-----+ +-------------------------------+ +--------------------+
+-------------------------+ | +----------------------+
+--> |ResourceConfiguration |
| +----------------------+
| +---------------------------+
+--> |MetaServerTaskConfiguration|
| +---------------------------+
| +---------------------+
+--> |ExecutorConfiguation |
| +---------------------+
| +--------------------+
+--> |MetaDBConfiguration |
+--------------------+
手機上閱讀如下:
關於handler配置,進一步細化如下圖
(Init)
+--> sessionConnectionHandler
|
|
+--> sessionNodeHandler
+-----------------------+ |
+---> | sessionServerHandlers +--+
+---------------------+ | +-----------------------+ +--> renewNodesRequestHandler
+--> | MetaServerBootstrap | | |
| +---------------------+ | |
| +---------------------------------+ | +--> getNodesRequestHandler
+--> |MetaServerConfigBeanConfiguration| | |
| +---------------------------------+ | |
| +------------------------------+ | +--> fetchProvideDataRequestHandler
+--> |MetaServerServiceConfiguration| |
| +------------------------------+ |
| +---------------------------------+ | +--> dataConnectionHandler
+--> |MetaServerRepositoryConfiguration+---+ |
| +---------------------------------+ | |
| +-------------------------------+ | +--------------------+ +--> getNodesRequestHandler
+-------------------------+ +--> |MetaServerRemotingConfiguration| +---> | dataServerHandlers +-----+
| MetaServerConfiguration | +-----+ +-------------------------------+ | +--------------------+ |
+-------------------------+ | +----------------------+ | +--> dataNodeHandler
+--> |ResourceConfiguration | | |
| +----------------------+ | |
| +---------------------------+ | +--> renewNodesRequestHandler
+--> |MetaServerTaskConfiguration| | |
| +---------------------------+ | |
| +---------------------+ | +--> fetchProvideDataRequestHandler
+--> |ExecutorConfiguation | |
| +---------------------+ |
| +--------------------+ |
+--> |MetaDBConfiguration | | +---> metaConnectionHandler
+--------------------+ | +--------------------+ |
+----> | metaServerHandlers +-----+
+--------------------+ +---> getNodesRequestHandler
手機上如圖
這個就對應了參考中的圖例:
MetaServer 在啟動時,會啟動三個 Bolt Server,並且註冊 Processor Handler,處理對應的請求:
- DataServer:處理 DataNode 相關的請求;
- SessionServer:處理 SessionNode 相關的請求;
- MetaServer:處理MetaNode相關的請求;
0x06 啟動
系統是通過對MetaServerBootstrap的控制來完成了啟動。
MetaServer 模組的各個 bean 在 JavaConfig 中統一配置,JavaConfig 類為 MetaServerConfiguration。
啟動入口類為 MetaServerInitializerConfiguration,該類不由 JavaConfig 管理配置,而是繼承了 SmartLifecycle 介面,在啟動時由 Spring 框架調用其 start 方法。
public class MetaServerInitializerConfiguration implements SmartLifecycle {
@Autowired
private MetaServerBootstrap metaServerBootstrap;
@Override
public void start() {
metaServerBootstrap.start();
MetaServerInitializerConfiguration.this.running.set(true);
}
@Override
public void stop() {
this.running.set(false);
metaServerBootstrap.destroy();
}
}
具體見下圖,因為 metaServerBootstrap 是通過配置生成,所以init過程指向配置部分。
(Init)
+------------------------------------+ start,stop
| MetaServerInitializerConfiguration +----------------+
+--------------+---------------------+ |
^ |
| |
| |
| |
| v
| +---------------------+
| +--> | MetaServerBootstrap |
+-------------------+ | | +---------------------+
| @EnableMetaServer | | | +---------------------------------+
| | | +--> |MetaServerConfigBeanConfiguration|
| MetaApplication | | | +---------------------------------+
+--------------+----+ | | +------------------------------+ +-----------------------+
| | +--> |MetaServerServiceConfiguration| +---> | sessionServerHandlers |
| | | +------------------------------+ | +-----------------------+
| | | +---------------------------------+ | +--------------------+
| | +--> |MetaServerRepositoryConfiguration+-------> | dataServerHandlers |
| | | +---------------------------------+ | +--------------------+
| | (Configuration) | +-------------------------------+ | +--------------------+
| +---------+---------------+ +--> |MetaServerRemotingConfiguration| +---> | metaServerHandlers |
+-----------> | MetaServerConfiguration | +-----+ +-------------------------------+ +--------------------+
+-------------------------+ | +----------------------+
+--> |ResourceConfiguration |
| +----------------------+
| +---------------------------+
+--> |MetaServerTaskConfiguration|
| +---------------------------+
| +---------------------+
+--> |ExecutorConfiguation |
| +---------------------+
| +--------------------+
+--> |MetaDBConfiguration |
+--------------------+
手機上如下
6.1 架構
MetaServerBootstrap是核心啟動類,該類主要包含了三類組件:外部節點通訊組件、Raft 服務通訊組件、定時器組件。
-
外部節點通訊組件:在該類中有幾個 Server 通訊對象,用於和其它外部節點進行通訊。其中 httpServer 主要提供一系列 http 介面,用於 dashboard 管理、數據查詢等;sessionServer 主要是處理一些session相關的服務;dataServer 則負責數據相關服務;metaServer 負責meta server的註冊;
-
Raft 服務 :用於集群間的變更和數據同步,raftExchanger 就起到這個作用;
-
定時器組件:例如定時檢測節點資訊、定時檢測數據版本資訊;具體可見 ExecutorManager,這是一個啟動各種管理執行緒的地方。他的啟動設置是在 MetaServerBootstrap.initRaft 之中 。
6.2 類定義
MetaServerBootstrap的定義如下:
public class MetaServerBootstrap {
@Autowired
private MetaServerConfig metaServerConfig;
@Autowired
private Exchange boltExchange;
@Autowired
private Exchange jerseyExchange;
@Autowired
private ExecutorManager executorManager;
@Resource(name = "sessionServerHandlers")
private Collection<AbstractServerHandler> sessionServerHandlers;
@Resource(name = "dataServerHandlers")
private Collection<AbstractServerHandler> dataServerHandlers;
@Resource(name = "metaServerHandlers")
private Collection<AbstractServerHandler> metaServerHandlers;
@Autowired
private ResourceConfig jerseyResourceConfig;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private RaftExchanger raftExchanger;
private Server sessionServer;
private Server dataServer;
private Server metaServer;
private Server httpServer;
}
可以參見下圖
+-----------------+
+----> | metaServerConfig|
| +-----------------+
| +--------------+
+----> | boltExchange |
| +--------------+
| +--------------+
+----> |jerseyExchange|
| +--------------+
| +---------------+
+----> |executorManager|
| +---------------+
| +---------------------+
+----> |sessionServerHandlers|
| +---------------------+
| +-----------------+
+----> |dataServerHandler|
(Init) | +-----------------+
+------------------------------------+ start,stop +---------------------+ | +------------------+
| MetaServerInitializerConfiguration +-------------> | MetaServerBootstrap | +--------> |metaServerHandlers|
+------------------------------------+ +---------------------+ | +------------------+
| +-------------+
+----> |raftExchanger|
| +-------------+
| +-------------+
+----> |sessionServer|
| +-------------+
| +-----------+
+----> |dataServer |
| +-----------+
| +-----------+
+----> |metaServer |
| +-----------+
| +----------+
+----> |httpServer|
| +----------+
| +---------------------+
+----> |jerseyResourceConfig |
| +---------------------+
| +-------------------+
+----> |applicationContext |
+-------------------+
手機參見下圖
6.3 通訊 Exchange
因為前面程式碼中有
@Autowired
private Exchange boltExchange;
@Autowired
private Exchange jerseyExchange;
這裡要特殊說明下Exchange。
Exchange 作為 Client / Server 連接的抽象,負責節點之間的連接。在建立連接中,可以設置一系列應對不同任務的 handler (稱之為 ChannelHandler),這些 ChannelHandler 有的作為 Listener 用來處理連接事件,有的作為 Processor 用來處理各種指定的事件,比如服務資訊數據變化、Subscriber 註冊等事件。
圖 – 每一層各司其職,協同實現節點通訊
各種節點在啟動的時候,利用 Exchange 設置了一系列 ChannelHandler,比如:
private void openDataRegisterServer() {
try {
if (dataStart.compareAndSet(false, true)) {
dataServer = boltExchange.open(new URL(NetUtil.getLocalAddress().getHostAddress(),
metaServerConfig.getDataServerPort()), dataServerHandlers
.toArray(new ChannelHandler[dataServerHandlers.size()]));
}
}
}
6.4 啟動入口
前面已經提到,啟動入口類為 MetaServerInitializerConfiguration,該類不由 JavaConfig 管理配置,而是繼承了 SmartLifecycle 介面,在啟動時由 Spring 框架調用其 start 方法。
該方法中調用了 MetaServerBootstrap # start 方法,用於啟動一系列的初始化服務。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.SmartLifecycle;
public class MetaServerInitializerConfiguration implements SmartLifecycle {
@Autowired
private MetaServerBootstrap metaServerBootstrap;『
@Override
public void start() {
metaServerBootstrap.start();
MetaServerInitializerConfiguration.this.running.set(true);
}
}
6.4.1 啟動服務Server
前面提到,MetaServerBootstrap 在啟動時,會啟動三個 Bolt Server,並且註冊 Processor Handler,處理對應的請求:
- DataServer:處理 DataNode 相關的請求;
- SessionServer:處理 SessionNode 相關的請求;
- MetaServer:處理MetaNode相關的請求;
然後啟動 HttpServer, 用於處理 Admin 請求,提供推送開關,集群數據查詢等 Http 介面。
最後啟動 Raft 服務, 每個節點同時作為 RaftClient 和 RaftServer, 用於集群間的變更和數據同步。為支援高可用特性,對於 MetaServer 來說,存儲了 SOFARegistry 的元數據,為了保障 MetaServer 集群的一致性,其採用了 Raft 協議來進行選舉和複製。
具體程式碼參見:
public void start() {
openSessionRegisterServer();
openDataRegisterServer();
openMetaRegisterServer();
openHttpServer();
initRaft();
}
private void openHttpServer() {
if (httpStart.compareAndSet(false, true)) {
bindResourceConfig();
httpServer = jerseyExchange.open(
new URL(NetUtil.getLocalAddress().getHostAddress(), metaServerConfig
.getHttpServerPort()), new ResourceConfig[] { jerseyResourceConfig });
}
}
private void initRaft() {
raftExchanger.startRaftServer(executorManager);
raftExchanger.startRaftClient();
raftExchanger.startCliService();
}
6.4.1.1 BoltServer
Raft 和 Bolt 是SOFA所特殊實現,所以我們暫不介紹其底層機制,以後有機會單獨開篇,下面提一下三個BoltServer。
private void openSessionRegisterServer() {
if (sessionStart.compareAndSet(false, true)) {
sessionServer = boltExchange
.open(
new URL(NetUtil.getLocalAddress().getHostAddress(), metaServerConfig
.getSessionServerPort()), sessionServerHandlers
.toArray(new ChannelHandler[sessionServerHandlers.size()]));
}
}
private void openDataRegisterServer() {
if (dataStart.compareAndSet(false, true)) {
dataServer = boltExchange.open(new URL(NetUtil.getLocalAddress().getHostAddress(),
metaServerConfig.getDataServerPort()), dataServerHandlers
.toArray(new ChannelHandler[dataServerHandlers.size()]));
}
}
private void openMetaRegisterServer() {
if (metaStart.compareAndSet(false, true)) {
metaServer = boltExchange.open(new URL(NetUtil.getLocalAddress().getHostAddress(),
metaServerConfig.getMetaServerPort()), metaServerHandlers
.toArray(new ChannelHandler[metaServerHandlers.size()]));
}
}
這幾個Server的handler就是我們前面配置的
@Resource(name = "sessionServerHandlers")
private Collection<AbstractServerHandler> sessionServerHandlers;
@Resource(name = "dataServerHandlers")
private Collection<AbstractServerHandler> dataServerHandlers;
@Resource(name = "metaServerHandlers")
private Collection<AbstractServerHandler> metaServerHandlers;
具體參見下圖:
+------------------------+
+-----------------+ +---> |sessionConnectionHandler|
+----> | metaServerConfig| | +------------------------+
| +-----------------+ | +-------------------+
| +--------------+ +---> |sessionNodeHandler |
+----> | boltExchange | +-->+ +-------------------+
| +--------------+ | | +------------------------+
| +--------------+ | +---> |renewNodesRequestHandler|
+----> |jerseyExchange| | | +------------------------+
| +--------------+ | | +----------------------+
| +---------------+ | +---> |getNodesRequestHandler|
+----> |executorManager| | | +----------------------+
| +---------------+ +---------------------+ | | +------------------------------+
+-------------------------------------> |sessionServerHandlers+---+ +---> |fetchProvideDataRequestHandler|
| +-------------+ +---------------------+ +------------------------------+
+----> |sessionServer| +------------------^
| +-------------+ +---------------------+
| ----> |dataConnectionHandler|
| +------------------+ | +---------------------+
+---------------------+ +-------------------------------------> |dataServerHandlers+----------+ +----------------------+
| MetaServerBootstrap | +---+ +-----------+ +------------------+ +---> |getNodesRequestHandler|
+---------------------+ +----> |dataServer +----------------------^ | +----------------------+
| +-----------+ | +---------------+
| +---> |dataNodeHandler|
| +------------------+ | +---------------+
+------------------------------------> |metaServerHandlers+------+ | +------------------------+
| +-----------+ +------------------+ | +---> |renewNodesRequestHandler|
+----> |metaServer +----------------------^ | | +------------------------+
| +-----------+ | | +------------------------------+
| | +---> |fetchProvideDataRequestHandler|
| +-------------+ | +------------------------------+
+----> |raftExchanger| |
| +-------------+ |
| |
| +----------+ |
+----> |httpServer| | +---------------------+
| +----------+ | +--> |metaConnectionHandler|
| +---------------------+ +----> +---------------------+
+----> |jerseyResourceConfig | | +---------------------+
| +---------------------+ +--> |getNodesRequestHandle|
| +-------------------+ +---------------------+
+----> |applicationContext |
+-------------------+
手機上
在初始化時候,大致堆棧如下 :
interest:55, RenewNodesRequestHandler (com.alipay.sofa.registry.server.meta.remoting.handler)
interest:61, SyncUserProcessorAdapter (com.alipay.sofa.registry.remoting.bolt)
registerUserProcessor:42, UserProcessorRegisterHelper (com.alipay.remoting.rpc.protocol)
registerUserProcessor:376, RpcServer (com.alipay.remoting.rpc)
registerUserProcessorHandler:159, BoltServer (com.alipay.sofa.registry.remoting.bolt)
initHandler:139, BoltServer (com.alipay.sofa.registry.remoting.bolt)
startServer:92, BoltServer (com.alipay.sofa.registry.remoting.bolt)
open:65, BoltExchange (com.alipay.sofa.registry.remoting.bolt.exchange)
open:36, BoltExchange (com.alipay.sofa.registry.remoting.bolt.exchange)
openSessionRegisterServer:149, MetaServerBootstrap (com.alipay.sofa.registry.server.meta.bootstrap)
start:108, MetaServerBootstrap (com.alipay.sofa.registry.server.meta.bootstrap)
start:51, MetaServerInitializerConfiguration (com.alipay.sofa.registry.server.meta.bootstrap)
以SessionServer為例,在構建過程中,調用到 BoltExchange . open。
private void openSessionRegisterServer() {
if (sessionStart.compareAndSet(false, true)) {
sessionServer = boltExchange
.open(
new URL(NetUtil.getLocalAddress().getHostAddress(), metaServerConfig
.getSessionServerPort()), sessionServerHandlers
.toArray(new ChannelHandler[sessionServerHandlers.size()]));
}
}
BoltExchange中有
@Override
public Server open(URL url, ChannelHandler... channelHandlers) {
BoltServer server = createBoltServer(url, channelHandlers);
setServer(server, url);
server.startServer();
return server;
}
BoltServer中有
public void startServer() {
if (isStarted.compareAndSet(false, true)) {
boltServer = new RpcServer(url.getPort(), true);
initHandler();
boltServer.start();
}
}
private void initHandler() {
if (initHandler.compareAndSet(false, true)) {
boltServer.addConnectionEventProcessor(ConnectionEventType.CONNECT,
new ConnectionEventAdapter(ConnectionEventType.CONNECT,
getConnectionEventHandler(), this));
boltServer.addConnectionEventProcessor(ConnectionEventType.CLOSE,
new ConnectionEventAdapter(ConnectionEventType.CLOSE, getConnectionEventHandler(),
this));
boltServer.addConnectionEventProcessor(ConnectionEventType.EXCEPTION,
new ConnectionEventAdapter(ConnectionEventType.EXCEPTION,
getConnectionEventHandler(), this));
registerUserProcessorHandler();
}
}
最後調用,會設定同步和非同步的handler。
private void registerUserProcessorHandler() {
if (channelHandlers != null) {
for (ChannelHandler channelHandler : channelHandlers) {
if (HandlerType.PROCESSER.equals(channelHandler.getType())) {
if (InvokeType.SYNC.equals(channelHandler.getInvokeType())) {
boltServer.registerUserProcessor(new SyncUserProcessorAdapter(
channelHandler));
} else {
boltServer.registerUserProcessor(new AsyncUserProcessorAdapter(
channelHandler));
}
}
}
}
}
6.4.1.2 HttpServer
以使用 Jetty 的 openHttpServer 為例
啟動 HttpServer, 用於處理 Admin 請求,提供推送開關,集群數據查詢等 Http 介面。
public class JerseyJettyServer implements Server {
public static org.eclipse.jetty.server.Server createServer(final URI uri,
final ResourceConfig resourceConfig,
final boolean start) {
JettyHttpContainer handler = ContainerFactory.createContainer(JettyHttpContainer.class,
resourceConfig);
int defaultPort = Container.DEFAULT_HTTP_PORT;
final int port = (uri.getPort() == -1) ? defaultPort : uri.getPort();
final org.eclipse.jetty.server.Server server = new org.eclipse.jetty.server.Server(
new JettyConnectorThreadPool());
final ServerConnector http = new ServerConnector(server, new HttpConnectionCustomFactory());
http.setPort(port);
server.setConnectors(new Connector[] { http });
if (handler != null) {
server.setHandler(handler);
}
if (start) {
try {
// Start the server.
server.start();
}
}
return server;
}
}
其堆棧如下:
<init>:72, JerseyJettyServer (com.alipay.sofa.registry.remoting.jersey)
open:73, JerseyExchange (com.alipay.sofa.registry.remoting.jersey.exchange)
open:40, JerseyExchange (com.alipay.sofa.registry.remoting.jersey.exchange)
openHttpServer:205, MetaServerBootstrap (com.alipay.sofa.registry.server.meta.bootstrap)
start:114, MetaServerBootstrap (com.alipay.sofa.registry.server.meta.bootstrap)
start:51, MetaServerInitializerConfiguration (com.alipay.sofa.registry.server.meta.bootstrap)
doStart:173, DefaultLifecycleProcessor (org.springframework.context.support)
access$200:50, DefaultLifecycleProcessor (org.springframework.context.support)
start:350, DefaultLifecycleProcessor$LifecycleGroup (org.springframework.context.support)
startBeans:149, DefaultLifecycleProcessor (org.springframework.context.support)
onRefresh:112, DefaultLifecycleProcessor (org.springframework.context.support)
finishRefresh:880, AbstractApplicationContext (org.springframework.context.support)
refresh:546, AbstractApplicationContext (org.springframework.context.support)
refresh:693, SpringApplication (org.springframework.boot)
refreshContext:360, SpringApplication (org.springframework.boot)
run:303, SpringApplication (org.springframework.boot)
run:1118, SpringApplication (org.springframework.boot)
run:1107, SpringApplication (org.springframework.boot)
main:42, MetaApplication (com.alipay.sofa.registry.server.meta)
6.4.1.3 @RaftService
如下存儲由Raft來保證數據一致性,後文針對此有詳細講解。
@RaftService(uniqueId = "sessionServer")
public class SessionVersionRepositoryService
@RaftService(uniqueId = "metaServer")
public class MetaRepositoryService
@RaftService(uniqueId = "dataServer")
public class DataRepositoryService
@RaftService(uniqueId = "sessionServer")
public class SessionRepositoryService
@RaftService(uniqueId = "dataServer")
public class DataConfirmStatusService
@RaftService(uniqueId = "sessionServer")
public class SessionConfirmStatusService
6.4.2 ExecutorManager
是一個啟動各種管理執行緒的地方,都是定期常規管理任務。
public class ExecutorManager {
private ScheduledExecutorService scheduler;
private ThreadPoolExecutor heartbeatCheckExecutor;
private ThreadPoolExecutor checkDataChangeExecutor;
private ThreadPoolExecutor getOtherDataCenterChangeExecutor;
private ThreadPoolExecutor connectMetaServerExecutor;
private ThreadPoolExecutor checkNodeListChangePushExecutor;
private ThreadPoolExecutor raftClientRefreshExecutor;
private MetaServerConfig metaServerConfig;
@Autowired
private Registry metaServerRegistry;
@Autowired
private MetaClientExchanger metaClientExchanger;
@Autowired
private RaftExchanger raftExchanger;
public void startScheduler() {
init();
scheduler.schedule(new TimedSupervisorTask("HeartbeatCheck", scheduler, heartbeatCheckExecutor,
metaServerConfig.getSchedulerHeartbeatTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerHeartbeatExpBackOffBound(), () -> metaServerRegistry.evict()),
metaServerConfig.getSchedulerHeartbeatFirstDelay(), TimeUnit.SECONDS);
scheduler.schedule(
new TimedSupervisorTask("GetOtherDataCenterChange", scheduler, getOtherDataCenterChangeExecutor,
metaServerConfig.getSchedulerGetDataChangeTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerGetDataChangeExpBackOffBound(), () -> {
metaServerRegistry.getOtherDataCenterNodeAndUpdate(NodeType.DATA);
metaServerRegistry.getOtherDataCenterNodeAndUpdate(NodeType.META);
}), metaServerConfig.getSchedulerGetDataChangeFirstDelay(), TimeUnit.SECONDS);
scheduler.schedule(new TimedSupervisorTask("ConnectMetaServer", scheduler, connectMetaServerExecutor,
metaServerConfig.getSchedulerConnectMetaServerTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerConnectMetaServerExpBackOffBound(),
() -> metaClientExchanger.connectServer()), metaServerConfig.getSchedulerConnectMetaServerFirstDelay(),
TimeUnit.SECONDS);
scheduler.schedule(
new TimedSupervisorTask("CheckSessionNodeListChangePush", scheduler, checkNodeListChangePushExecutor,
metaServerConfig.getSchedulerCheckNodeListChangePushTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerCheckNodeListChangePushExpBackOffBound(),
() -> metaServerRegistry.pushNodeListChange(NodeType.SESSION)),
metaServerConfig.getSchedulerCheckNodeListChangePushFirstDelay(), TimeUnit.SECONDS);
scheduler.schedule(
new TimedSupervisorTask("CheckDataNodeListChangePush", scheduler, checkNodeListChangePushExecutor,
metaServerConfig.getSchedulerCheckNodeListChangePushTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerCheckNodeListChangePushExpBackOffBound(),
() -> metaServerRegistry.pushNodeListChange(NodeType.DATA)),
metaServerConfig.getSchedulerCheckNodeListChangePushFirstDelay(), TimeUnit.SECONDS);
scheduler.schedule(new TimedSupervisorTask("RaftClientRefresh", scheduler, raftClientRefreshExecutor,
metaServerConfig.getSchedulerCheckNodeListChangePushTimeout(), TimeUnit.SECONDS,
metaServerConfig.getSchedulerCheckNodeListChangePushExpBackOffBound(),
() -> raftExchanger.refreshRaftClient()),
metaServerConfig.getSchedulerCheckNodeListChangePushFirstDelay(), TimeUnit.SECONDS);
}
}
6.4.2.1 啟動
ExecutorManager 的啟動設置是在 MetaServerBootstrap.initRaft 之中,分別啟動RaftServer,RaftClient,CliService。
private void initRaft() {
raftExchanger.startRaftServer(executorManager);
raftExchanger.startRaftClient();
raftExchanger.startCliService();
}
當 Raft 選出 Leader 之後,會調用到 ExecutorManager # startScheduler。
- 首先生成各個ThreadPoolExecutor;
- 然後運行本身的 各個TimedSupervisorTask,其會調用不同的handler,比如 connectServer,getSchedulerHeartbeatFirstDelay 等等;
6.4.2.2 TimedSupervisorTask
TimedSupervisorTask 實現了 TimerTask。
public class TimedSupervisorTask extends TimerTask {
private final ScheduledExecutorService scheduler;
private final ThreadPoolExecutor executor;
private final long timeoutMillis;
private final Runnable task;
private String name;
private final AtomicLong delay;
private final long maxDelay;
@Override
public void run() {
Future future = null;
try {
future = executor.submit(task);
// block until done or timeout
future.get(timeoutMillis, TimeUnit.MILLISECONDS);
delay.set(timeoutMillis);
} catch {
.....
} finally {
if (future != null) {
future.cancel(true);
}
scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
}
}
}
6.4.2.3 管理任務
可以看到管理任務大致有:
- HeartbeatCheck :心跳檢測;
- GetOtherDataCenterChange :查看其他數據中心的變化;
- ConnectMetaServer :與其他的MetaServer交互;
- CheckSessionNodeListChangePush :看看Session節點的變化;
- CheckDataNodeListChangePush :查看數據節點變化;
- RaftClientRefresh :看看Raft 服務消息;
TimedSupervisorTask 會定期執行,比如 CheckDataNodeListChangePush 這個執行緒會定期執行 metaServerRegistry.pushNodeListChange(NodeType.DATA)) 來看看是否有變化。這裡就會用到DataNode註冊時候,Confirm的消息。
@Override
public void pushNodeListChange() {
NodeOperator<DataNode> fireNode;
if ((fireNode = dataConfirmStatusService.peekConfirmNode()) != null) {
NodeChangeResult nodeChangeResult = getNodeChangeResult();
Map<String, Map<String, DataNode>> map = nodeChangeResult.getNodes();
Map<String, DataNode> addNodes = map.get(nodeConfig.getLocalDataCenter());
if (addNodes != null) {
Map<String, DataNode> previousNodes = dataConfirmStatusService.putExpectNodes(
fireNode.getNode(), addNodes);
if (!previousNodes.isEmpty()) {
firePushDataListTask(fireNode, nodeChangeResult, previousNodes, true);
}
}
firePushSessionListTask(nodeChangeResult, fireNode.getNodeOperate().toString());
}
}
再比如定期去除過期的Node:
public class MetaServerRegistry implements Registry<Node> {
@Override
public void evict() {
for (NodeType nodeType : NodeType.values()) {
StoreService storeService = ServiceFactory.getStoreService(nodeType);
if (storeService != null) {
Collection<Node> expiredNodes = storeService.getExpired();
if (expiredNodes != null && !expiredNodes.isEmpty()) {
storeService.removeNodes(expiredNodes);
}
}
}
}
}
6.4.3 ServiceFactory
ServiceFactory 需要特殊說明,它提供了系統所需要的一系列服務。特殊之處在於,ServiceFactory 不是由 MetaServerBootstrap 負責啟動,而是由 Spring 負責啟動。因為 ServiceFactory 繼承了ApplicationContextAware,所以啟動時候生成。
在Web應用中,Spring容器通常採用聲明式方式配置產生:開發者只要在web.xml中配置一個Listener,該Listener將會負責初始化Spring容器,MVC框架可以直接調用Spring容器中的Bean,無需訪問Spring容器本身。在這種情況下,容器中的Bean處於容器管理下,無需主動訪問容器,只需接受容器的依賴注入即可。
但在某些特殊的情況下,Bean需要實現某個功能,但該功能必須藉助於Spring容器才能實現,此時就必須讓該Bean先獲取Spring容器,然後藉助於Spring容器實現該功能。為了讓Bean獲取它所在的Spring容器,可以讓該Bean實現ApplicationContextAware介面。
下面程式碼可以看出來,啟動了一系列服務。
public class ServiceFactory implements ApplicationContextAware {
private static Map<NodeType, StoreService> storeServiceMap = new HashMap<>();
private static Map<NodeType, NodeConnectManager> connectManagerMap = new HashMap<>();
private static Map<NodeType, NodeService> nodeServiceMap = new HashMap<>();
}
storeServiceMap = {HashMap@5107} size = 3
{Node$NodeType@5525} "SESSION" -> {SessionStoreService@5526}
key = {Node$NodeType@5525} "SESSION"
value = {SessionStoreService@5526}
{Node$NodeType@4815} "DATA" -> {DataStoreService@5527}
key = {Node$NodeType@4815} "DATA"
value = {DataStoreService@5527}
{Node$NodeType@5528} "META" -> {MetaStoreService@5529}
key = {Node$NodeType@5528} "META"
value = {MetaStoreService@5529}
connectManagerMap = {HashMap@5532} size = 3
{Node$NodeType@5525} "SESSION" -> {SessionConnectionHandler@5548}
key = {Node$NodeType@5525} "SESSION"
value = {SessionConnectionHandler@5548}
{Node$NodeType@4815} "DATA" -> {DataConnectionHandler@5549}
key = {Node$NodeType@4815} "DATA"
value = {DataConnectionHandler@5549}
{Node$NodeType@5528} "META" -> {MetaConnectionHandler@5550}
key = {Node$NodeType@5528} "META"
value = {MetaConnectionHandler@5550}
nodeServiceMap = {HashMap@5533} size = 3
{Node$NodeType@5525} "SESSION" -> {SessionNodeServiceImpl@5540}
key = {Node$NodeType@5525} "SESSION"
value = {SessionNodeServiceImpl@5540}
{Node$NodeType@4815} "DATA" -> {DataNodeServiceImpl@5541}
key = {Node$NodeType@4815} "DATA"
value = {DataNodeServiceImpl@5541}
{Node$NodeType@5528} "META" -> {MetaNodeServiceImpl@5542}
key = {Node$NodeType@5528} "META"
value = {MetaNodeServiceImpl@5542}
至此,MetaServer的架構和啟動介紹完成,我們下文將會介紹基本功能,比如註冊,存儲,續約等。
0xFF 參考
服務註冊中心 MetaServer 功能介紹和實現剖析 | SOFARegistry 解析
服務註冊中心如何實現 DataServer 平滑擴縮容 | SOFARegistry 解析
服務註冊中心數據一致性方案分析 | SOFARegistry 解析
服務註冊中心如何實現秒級服務上下線通知 | SOFARegistry 解析
服務註冊中心 Session 存儲策略 | SOFARegistry 解析
服務註冊中心數據分片和同步方案詳解 | SOFARegistry 解析
服務註冊中心 SOFARegistry 解析 | 服務發現優化之路
海量數據下的註冊中心 – SOFARegistry 架構介紹
詳解螞蟻金服 SOFAJRaft | 生產級高性能 Java 實現
怎樣打造一個分散式資料庫——rocksDB, raft, mvcc,本質上是為了解決跨數據中心的複製
SOFAJRaft 實現原理 – 生產級 Raft 演算法庫存儲模組剖析