A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials
問題的來源
配置是軟件開發(fā)中一個古老而有用的概念, 我們需要通過配置來控制代碼運行的方式往衷,比如緩存時間偎球,數(shù)據(jù)庫地址等等。長久以來我們使用配置文件來記錄配置項,軟件在啟動時讀取配置文件并將配置項加載到內存中眶拉, 在軟件運行過程中就可以從內存中讀取配置項筐咧。如果需要修改配置項,只需要修改配置文件并且重新發(fā)布服務就可以了脆贵,這個方式被沿用了幾十年直到分布式系統(tǒng)伴隨著互聯(lián)網(wǎng)時代的到來医清。因為對于一個分布式應用重新發(fā)布服務意味著重新啟動分布在幾十臺甚至幾千臺服務器上的服務,但是重新發(fā)布系統(tǒng)耗時耗力而且可能會引起整個系統(tǒng)的波動卖氨,這顯然不是一個優(yōu)雅的方式会烙。為此需要一種動態(tài)調整配置而不需要重啟應用的方式负懦。
動態(tài)配置
為了解決上面提到的問題,動態(tài)配置出現(xiàn)了柏腻,動態(tài)配置包含兩個概念:
- 配置與代碼區(qū)分開來對待纸厉,配置不再是代碼的一部分,配置可以進行獨立更新五嫂,同樣的代碼在不同的配置下可以表現(xiàn)出不同的功能颗品。
- 配置更新與代碼更新嚴格區(qū)分,配置不再與DI/DC
在微服務實踐當中沃缘,服務注冊發(fā)現(xiàn)與配置中心是必要的兩個基礎服務躯枢。JAVA因為有Netflix公司開源的一套微服務生態(tài)加上與Spring架構的無縫銜接,可以很方便的使用這兩個組件槐臀,在Python當中還沒有一套完整的生態(tài)可以利用锄蹂。
所以我們選擇基于開源項目自己來開發(fā)一些對Python友好的組件,今天介紹下為Consul開發(fā)的Python客戶端峰档。
Consul是一個開源的分布式K/V系統(tǒng)败匹,可以用來實現(xiàn)服務注冊發(fā)現(xiàn)與配置中心。下面是consul的架構圖讥巡,和etcd掀亩,zookeeper等一樣,consul運行幾個server節(jié)點來維護系統(tǒng)一致性欢顷,并且對外提供服務槽棍,我們使用了restful api。另外在每臺服務器上可以運行一個agent節(jié)點來轉發(fā)請求到server集群抬驴,這樣服務可以不用知道consul集群的具體位置了炼七。
剛才提到了我們使用了consul的restful api,這些api可以將我們的服務接入到conusl集群布持。但是在實踐的過程中我們發(fā)現(xiàn)只有api接入是不夠的豌拙,為此我們自己開發(fā)了一個客戶端實現(xiàn)如下的功能:
實時推送數(shù)據(jù)到微服務內存
出于性能的考慮,微服務不應該直接請求consul集群來獲取最新的數(shù)據(jù)题暖,因為傳統(tǒng)的配置文件可以加載數(shù)據(jù)到內存中按傅,服務直接讀取內存中的數(shù)據(jù)是最快的選擇,但是傳統(tǒng)配置文件修改后需要重新啟動服務胧卤,而consul的優(yōu)勢在于不需要重新啟動服務就可以獲取最新的配置唯绍,那有沒有什么辦法可以讓我們將數(shù)據(jù)存放到consul集群的同時也能達到從內存中讀取最新配置的速度呢?為此我們在每個微服務啟動之前啟動一個進程來專門維護這個服務對應的配置數(shù)據(jù)到內存中枝誊,并將這塊內存共享給本機的微服務使用况芒。這個進程稱為watch進程,watch進程會向consul集群發(fā)送請求最新數(shù)據(jù)并記下這份數(shù)據(jù)的index,在下一次發(fā)送獲取最新數(shù)據(jù)請求時會帶上這個index兵钮,consul集群收到請求后會阻塞請求30秒,在這30秒內如果數(shù)據(jù)有變化就立即返回喘批,如果在30秒后沒有變化就返回timeout斷開連接皮壁,watch進程如果收到返回就更新內存中的數(shù)據(jù)椭更,如果收到timeout就發(fā)起下一次請求哪审。這樣通過watch進程就可以實時的將最新數(shù)據(jù)保存到內存當中了蛾魄,本機的微服務進程就可以從內存中讀取最新的數(shù)據(jù),并且對于微服務而言這一切都是無感知而且高效的湿滓。
定時同步與本地備份
上面說了watch進程滴须,雖然watch運行良好,但我們認為不應該只依靠這一個機制叽奥,我們需要保證在watch進程掛掉后微服務還能從內存中獲取新的數(shù)據(jù)扔水。為此我們在服務啟動之前會再啟動一個心跳進程,心跳進程會每隔15分鐘獲取一次全量數(shù)據(jù)朝氓,并將數(shù)據(jù)更新到內存中魔市,這樣我們的服務就有了雙保險,watch與心跳機制任何一個失效都不會影響到微服務赵哲。
但是還有一種情況會影響到服務的運行待德,那就是當consul集群不可用時,雖然這發(fā)生的概率很低枫夺,但我們依然要將這種情況考慮進去将宪,為此心跳進程在更新內存之后會將數(shù)據(jù)更新到本地的文件中,當整個consul集群都不可用時橡庞,如果微服務不重啟依然可以從內存中獲取最后一份數(shù)據(jù)较坛,即使微服務重啟也可以從本地文件中讀取備份數(shù)據(jù)到內存中。第三重機制保障了微服務不會受到consul集群可用性的影響扒最。
客戶端的部署
具有這三重機制的客戶端如何部署呢丑勤,是不是要在每臺服務器上都部署一份呢?我們并沒有這樣選擇吧趣,原因有兩點法竞,1.這會增加運維成本,這是我們不愿意看到的 2. 客戶端是為本機的服務提供代理服務的所以沒有必要設計成常駐的進程
所以我們將客戶端的啟動嵌入到微服務的啟動中再菊,一旦微服務代碼中使用了consul服務爪喘,客戶端就會在微服務之前啟動,并讀取一份最新的配置到內存中纠拔,緊接著我們的微服務就可以啟動了秉剑,同樣的在微服務關閉之后客戶端進程也會跟著關閉,這樣做的原因是我們的服務器并非固定發(fā)布一種服務稠诲,所以我們自然不希望在服務發(fā)布后有其他微服務的consul客戶端還在繼續(xù)運行侦鹏。通過這種方式我們的客戶端在不增加任何運維成本的前提下提供了consul集群的代理服務诡曙。