這是小卷對分佈式系統架構學習的第7篇文章,前面已經講了很多理論知識,今天結合具體的中間件來講分佈式配置中心
1.面試官提問
面試官:假設你是公司的基礎架構部門,現在需要設計內部的配置中心中間件,你要怎麼設計?
我:設計客户端和服務端,客户端集成到業務項目中,項目啓動時從服務端pull配置加載到本地,並且定時check服務端和本地配置是否一致,服務端如有更新,再pull到本地
面試官:那如果有幾萬台服務器,都是這樣定時去check,服務端壓力豈不是很大,要怎麼解決呢?
我:那改成用服務端push的方式???
面試官:......
面試官:那今天就到這裏吧,你回去等通知吧......
2.為什麼需要分佈式配置中心
不瞭解底層原理的小卷只好回家後苦心專研分佈式配置中心的原理,一定要弄清楚底層邏輯,下次要吊打面試官。
先來簡單理解為什麼需要配置中心?
我們開發的服務都是單體架構時,配置文件就和代碼放在一起,如springboot的application.yml文件,對配置的修改只需要修改這一個文件就行。到分佈式服務中,一個服務會有多台機器,不可能每個機器都單獨修改配置文件,然後重新部署的。
這就要用到配置中心了,以nacos為例,下圖是配置修改時和服務器間的操作:
3.開源框架
這裏列舉4種分佈式配置中心的中間件,我們直接從一箇中間件的原理來學習配置中心。
工作這麼多年,應該得了解一些開源組件,大大小小的都行:
1、Apollo
2016年5月,攜程開源的配置管理中心,具備規範的權限、流程治理等特性。
GitHub地址:https://github.com/apolloconfig/apollo
2、spring cloud config
2014年9月開源,Spring Cloud 生態組件,可以和Spring Cloud體系無縫整合。
3、Nacos
2018年6月,阿里開源的配置中心,也可以做DNS和RPC的服務發現。
4、Diamond
Diamond 出自淘寶,開源地址 【https://github.com/takeseem/diamond】 ,阿里集團內部的配置中心仍然用的diamond,只是開源版本不再維護
面試時可能會問到為什麼選擇Apollo作為配置中心?不用其他的配置中心呢?
很多人用的時候就是看別人也這麼用,或者大家都這麼用,就選擇了這個中間件。這裏如果遇到了的話,就可以提到開源社區的活躍性,因為Apollo 的社區生態活躍,且使用的公司特別多,常見的坑基本都被踩完了,所以選用Apollo。
4. Apollo工作原理
4.1基礎模型
Apollo文檔:Apollo配置中心設計,工作原理比較簡單:
- 用户在配置中心對配置進行修改併發布
- 配置中心通知Apollo客户端有配置更新
- Apollo客户端從配置中心拉取最新配置,更新本地配置並通知到應用
4.2架構模塊
解釋下各個模塊的功能:
- Config Service提供配置的讀取、推送等功能,服務對象是Apollo客户端
- Admin Service提供配置的修改、發佈功能,服務對象是Apollo Portal(管理界面)
- Config Service和Admin Service都需要註冊到Eureka並保持心跳;
- Meta Server是對Eureka做了一層封裝,封裝的是服務發現接口;
- Client通過域名訪問Meta Server獲取Config Service服務列表,即獲取IP+端口,然後通過IP+端口訪問服務,同時Client端自己做負載均衡,錯誤重試;
- Portal訪問Meta Server獲取Admin Service服務列表,也是獲取IP+端口,然後訪問服務,Portal側也做負載均衡;
5. 使用Apollo
官方有提供快速部署使用文檔:Quick Start
具體操作步驟可以自行查看官方文檔,這裏我們主要通過簡單使用Apollo來理解配置中心。部署完成後,登陸Apollo的管理界面,然後創建個應用,發佈後再創建個配置,接着再次發佈,如下圖:
這裏我是在本地啓動的,訪問http://localhost:8080/可以查看已註冊的實例
然後創建一個Springboot應用連接到Apollo配置中心,這裏不寫那麼具體了,可以自行參考官方的Java客户端使用指南
Mac電腦需先在本地的/opt/settings/server.properties文件中配置環境env=DEV,然後在application.properties文件中配置Apollo相關的內容如下:
# 接入Apollo配置
app.id=multi_function
apollo.meta=http://localhost:8080
# Apollo本地緩存路徑
apollo.cache-dir=/Users/longbig/log
# 指定Apollo配置文件的環境
env=DEV
# 配置訪問秘鑰
apollo.accesskey.secret=4c61a00512ad4cc09ef8a0e1ee672d89
apollo.bootstrap.enabled=true
為了測試客户端接收到配置中心配置變更的事件,我們參考官方文檔的代碼寫個監聽器的代碼如下:
@Configuration
@Slf4j
public class ApolloConfig {
@Bean
public void init() {
Config config = ConfigService.getAppConfig();
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
log.info("Changes for namespace " + changeEvent.getNamespace());
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
log.info(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()));
}
}
});
}
}
最後測試驗證,在管理界面增加一個配置,然後對配置修改發佈,可以看到客户端已經接收到配置變更的事件了,並且打印出日誌信息了
6. 配置發佈後實時生效設計
從上面簡單使用中可以看到,配置發佈後實時推送到客户端。下面我們簡要看一下這塊是怎麼設計實現的
配置發佈的大致過程:
- 用户在Portal操作配置發佈
- Portal調用Admin Service的接口操作發佈
- Admin Service發佈配置後,發送ReleaseMessage給各個Config Service
- Config Service收到ReleaseMessage後,通知對應的客户端
6.1發送ReleaseMessage的實現方式
從上圖看,應該是用MQ的方式比較合適,但是Apollo沒有用外部消息中間件,而是通過數據庫來實現這個簡單的消息隊列的。具體如下:
- Admin Service在配置發佈後會往ReleaseMessage表插入一條消息記錄,消息內容就是配置發佈的AppId+Cluster+Namespace
- Config Service有一個線程會每秒掃描一次ReleaseMessage表,看看是否有新的消息記錄
- Config Service如果發現有新的消息記錄,那麼就會通知到所有的消息監聽器(ReleaseMessageListener)
- 消息監聽器得到配置發佈的AppId+Cluster+Namespace後,會通知對應的客户端
我們查看數據庫的ReleaseMessage和ReleaseHistory表,可以查看到當前消息和歷史消息
6.2 Config Service通知客户端的實現方式
這裏能解釋説明開頭的面試題,客户端更新配置是Pull還是Push的方式?
具體實現方式如下:
- 客户端會發起一個Http請求到Config Service的
notifications/v2接口,也就是NotificationControllerV2 - NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起
- 如果在60秒內沒有該客户端關心的配置發佈,那麼會返回Http狀態碼304給客户端
- 如果有該客户端關心的配置發佈,NotificationControllerV2會調用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回。客户端從返回的結果中獲取到配置變化的namespace後,會立即請求Config Service獲取該namespace的最新配置。
7. 客户端的工作原理
接着講講Apollo客户端的工作原理:
- 客户端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現)
-
客户端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,為了防止推送機制失效導致配置不更新
- 客户端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率默認為每5分鐘拉取一次,客户端也可以通過在運行時指定System Property:
apollo.refreshInterval來覆蓋,單位為分鐘。
- 客户端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
-
客户端會把從服務端獲取到的配置在本地文件系統緩存一份
- 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
- 應用程序可以從Apollo客户端獲取最新的配置、訂閲配置更新通知
8.題外話
之前在第一家公司工作過程中,遇到個問題是:對應用某個配置的變更如何通知到生產環境的所有機器?
當時的場景是前端發起HTTP請求,調用後端接口修改配置,因為負載均衡的緣故,請求只會打到1台機器上,只有1台機器的內存配置被更新,其他機器的內存配置還是舊的,當時小組一起討論解決辦法,可能認知有限,只想到MQ等等方式,沒想到配置中心的原理
後來去了阿里之後,參與過寫配置中心配置變更監聽器,實現了全量機器的內存配置更新功能
現在回想起來,當時沒解決的原因還是認知不夠,現在學了配置中心的原理又想到了這件事,分享給大家學習參考~
相信通過學習Apollo配置中心的原理,你在面試過程中如果遇到開頭的題目,應該也能説上一二了。