Python微服務實踐-集成Consul配置中心


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.

配置是軟件開發中一個古老而有用的概念,我們需要通過配置來控制代碼運行的方式,比如緩存時間,數據庫地址等等。

長久以來我們使用配置文件來保存配置項,軟件在啟動時讀取配置文件並將配置項加載到內存中, 在軟件運行過程中就可以從內存中讀取配置項。如果需要修改,只需修改配置文件並且重新發布服務就可,這個方式被沿用了幾十年直到分布式系統伴隨着互聯網時代的到來。

對於一個分布式應用來說重新發布服務意味着重新啟動分布在幾十台甚至幾千台服務器上的服務,但是重新發布整個系統是一個耗時耗力過程而且可能會引起整個系統的波動,這顯然不優雅。為此需要一種可以動態調整配置而不需要重啟應用的方式。

 

動態配置

動態配置主要包含兩個概念:

配置與代碼區分對待,配置不再是代碼的一部分,同樣的代碼在不同的配置下可以表現出不同的功能;

配置更新與代碼更新嚴格區分,配置不再與DI/DC流程耦合,配置可以獨立進行更新。

配置中心實現了這兩個概念,通過一個獨立的基礎服務為微服務提供集中式的配置管理。

配置中心與Python

對於JAVA應用來說,配置中心與開發框架的集成已經很成熟,比如Spring或者Spring Boot集成了Netflix開源的Archaius與Spring cloud,另外比如攜程開源的Apollo配置中心也可以集成進Spring框架。

但是對於Python服務開發者而言,配置中心與開發框架的集成還沒有一個成熟的開源方案,為了優雅的使用配置中心實現動態配置,我們為Python開發框架集成了Consul客戶端。

Consul配置中心

先簡單介紹下Consul本身。Consul是一個開源的分布式K/V系統,可以用來實現服務注冊發現與配置中心。下面是Consul的架構圖。

 

和etcd、zookeeper等一樣,Consul運行幾個server節點來維護系統一致性,配置項即存儲在server節點的內存中並且對外提供讀寫服務,另外在每台服務器上可以運行一個agent節點來轉發請求到server集群,這樣服務可以不用知道Consul集群的具體位置。我們就是使用了agent節點提供的restful api來讀取最新的配置數據。這些api可以將我們的服務接入到Consul集群。

實踐的過程中只有api接入是遠遠不夠的,為此我們從性能、可靠性、運維三個方面出發,還設計實現了實時推送、定時同步與本地備份、部署客戶端三大功能。

實時推送

傳統的配置文件可以加載配置項到內存中,服務直接讀取內存中的配置項最簡單最快速,但是傳統配置文件修改后需要重新啟動服務,配置中心克服了這個缺點但不能以性能損失為代價,我們仍然希望讀取配置的過程簡單快速。出於性能的考慮,服務不應該直接請求Consul集群來獲取最新的數據,那有沒有什么辦法可以讓我們將數據存放到Consul集群的同時也能達到從內存中讀取最新配置的速度呢?

我們在每個服務啟動之前啟動一個進程來專門維護這個服務對應的配置數據到內存中,並將這塊內存通過IPC的方式共享給本機的服務使用。這個進程稱為watch進程。

 

watch進程會向Consul集群發送請求讀取最新的配置項並記下這份數據的index,在下一次發送請求時會帶上這個index,Consul集群收到請求后會阻塞請求30秒,在這30秒內如果數據有變化就立即返回,如果在30秒后沒有變化就返回timeout斷開連接,watch進程如果收到返回就更新內存中的數據,如果收到timeout就發起下一次請求。這樣通過watch進程就可以實時的將最新數據維護在內存當中了,因為服務進程是通過IPC的方式從本機內存中讀取配置項,所以讀取速度可以與從進程內存中讀取相當。並且我們將這個過程封裝起來,對外暴露一個代理對象,將這個對象注入到服務進程中,所以對於服務的開發者而言,讀取最新的配置項就是讀取這個代理對象的屬性,非常簡單透明。

watch進程本身實際上就是一個事件循環,所以我們采用tornado提供的IoLoop來實現watch進程,並通過tornado的coroutine實現了異步的request,我們保證watch進程足夠簡單進而足夠可靠。

定時同步與本地備份

如何保證在watch進程掛掉后服務還能從內存中獲取新的數據?

我們在服務啟動之前會再啟動一個心跳進程,心跳進程會每隔15分鍾獲取一次全量數據,並將數據更新到內存中,這樣我們的服務就有了雙保險,watch與心跳機制任何一個失效都不會影響到微服務。

但是還有一種情況會影響到服務的運行,那就是當Consul集群不可用時,雖然這發生的概率很低。為此心跳進程在更新完內存之后會將數據再更新到本地的文件中,當整個Consul集群都不可用時,如果服務不重啟依然可以從內存中獲取最后一份數據,即使服務重啟也可以從本地文件中讀取備份數據到內存中。第三重機制保障了服務的可用性不會受到Consul集群可用性的影響。

 

客戶端的部署

如何部署這個客戶端呢?最直接的方式是在每台服務器上都部署一份客戶端Agent。但我們並沒有這樣選擇,原因有兩點:首先這會增加運維成本,這是我們不願意看到的,其次客戶端是為本機的服務提供代理服務的所以沒有必要設計成常駐的進程。

所以我們將客戶端的啟動嵌入到服務的啟動中,一旦服務代碼中聲明使用了Consul配置中心,客戶端就會在服務啟動之前啟動,並讀取一份最新的配置到內存中,緊接着我們的服務就可以啟動了,同樣的在服務關閉之后客戶端進程也會跟着關閉,這樣做的原因是我們的服務器並非固定發布一種服務,所以我們自然不希望在服務發布后有其他服務的Consul客戶端還在繼續運行。通過這種方式我們的客戶端在不增加任何運維成本的前提下提供了Consul集群的代理服務。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM