1、總體架構
上圖簡要描述了Apollo的總體設計,我們可以從下往上看:
- Config Service提供配置的讀取、推送等功能,服務對象是Apollo客戶端
- Admin Service提供配置的修改、發佈等功能,服務對象是Apollo Portal(管理界面)
- Config Service和Admin Service都是多實例、無狀態部署,所以需要將自己註冊到Eureka中並保持心跳
- 在Eureka之上我們架了一層Meta Server用於封裝Eureka的服務發現接口
- Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Client側會做load balance、錯誤重試
- Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Portal側會做load balance、錯誤重試
- 爲了簡化部署,我們實際上會把Config Service、Eureka和Meta Server三個邏輯角色部署在同一個JVM進程中
2、服務角色
(1)apollo-configservice:提供配置獲取接口,提供配置更新推送接口,接口服務對象爲Apollo客戶端
(2)apollo-adminservice:提供配置管理接口,提供配置修改、發佈等接口,接口服務對象爲Portal,以及Eureka
(3)apollo-portal:提供Web界面供用戶管理配置
(4)apollo-client:Apollo提供的客戶端程序,爲應用提供配置獲取、實時更新等功能
3、配置發佈流程
上圖簡要描述了配置發佈的大致過程:
- 用戶在Portal操作配置發佈
- Portal調用Admin Service的接口操作發佈
- Admin Service發佈配置後,發送ReleaseMessage給各個Config Service
- Config Service收到ReleaseMessage後,通知對應的客戶端
Admin Service 與 Config Service 異步通知流程
Admin Service 在配置發佈後,需要通知所有的 Config Service 有配置發佈,從而 Config Service 可以通知對應的客戶端來拉取最新的配置。從概念上來看,這是一個典型的消息使用場景,Admin Service 作爲 producer 發出消息,各個Config Service 作爲 consumer 消費消息。通過一個消息組件(Message Queue)就能很好的實現 Admin Service 和 Config Service 的解耦。在實現上,考慮到 Apollo 的實際使用場景,以及爲了儘可能減少外部依賴,我們沒有采用外部的消息中間件,而是通過數據庫實現了一個簡單的消息隊列。
實現方式:
- Admin Service 在配置發佈後會往 ReleaseMessage 表插入一條消息記錄,消息內容就是配置發佈的 AppId+Cluster+Namespace ,參見 DatabaseMessageSender 。
- Config Service 有一個線程會每秒掃描一次 ReleaseMessage 表,看看是否有新的消息記錄,參見 ReleaseMessageScanner 。
- Config Service 如果發現有新的消息記錄,那麼就會通知到所有的消息監聽器(ReleaseMessageListener),如 NotificationControllerV2 ,消息監聽器的註冊過程參見 ConfigServiceAutoConfiguration 。
- NotificationControllerV2 得到配置發佈的 AppId+Cluster+Namespace 後,會通知對應的客戶端。
示意圖:
客戶端設計
上圖簡要描述了Apollo客戶端的實現原理:
- 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。
- 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,爲了防止推送機制失效導致配置不更新
- 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率默認爲每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property:
apollo.refreshInterval
來覆蓋,單位爲分鐘。
- 客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
- 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
- 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
- 應用程序從Apollo客戶端獲取最新的配置、訂閱配置更新通知
配置更新推送實現
前面提到了Apollo客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。
長連接實際上我們是通過Http Long Polling實現的,具體而言:
- 客戶端發起一個Http請求到服務端
- 服務端會保持住這個連接60秒
- 如果在60秒內有客戶端關心的配置變化,被保持住的客戶端請求會立即返回,並告知客戶端有配置變化的namespace信息,客戶端會據此拉取對應namespace的最新配置
- 如果在60秒內沒有客戶端關心的配置變化,那麼會返回Http狀態碼304給客戶端
- 客戶端在收到服務端請求後會立即重新發起連接,回到第一步
考慮到會有數萬客戶端向服務端發起長連,在服務端我們使用了async servlet(Spring DeferredResult)來服務Http Long Polling請求。