為什麼不用原生 Spring Cloud Config?
引言
近幾年傳統應用架構已經逐漸朝著微服務架構演進。那麼隨著業務的發展,微服務越來越龐大,此時服務配置的管理變得會複雜起來。為了方便服務配置檔案統一管理,實時更新,配置中心應運而生。
其實,所謂配置中心,就是將配置的資料放在某種儲存介質中,該介質可以是
-
File(例如Git、Svn)
-
Database(例如mysql、oracle)
-
nosql Database(例如Redis、Memacache、MongoDb)
-
其他第三方中介軟體(例如Zookeeper)
那麼配置中心可以簡單理解為是封裝了對這些介質進行操作的介面,供客戶端拉取使用。
由於我們採用的是Spring-Boot的架構,因此當時自然而然會考慮到Spring-Cloud中提供的配置中心 Spring-Cloud-Config ,但是當時做完調研以後,覺得並不能直接用。
因此,本文想來分享一下,原生 Spring-Cloud-Config的配置中心的缺點,以及我們對
Spring-Cloud-Config
做了哪些改動。
正文
OK,我們當時做配置中心的選型的時候。第一選擇是Spring-Cloud- Config。
Spring-Cloud-Config在儲存介質的選擇這塊,基本上網上所有的文章都在推薦使用Git,即將配置檔案放在Git中,服務端從Git中讀取。其實官網上講的最詳細的配置,也是採取用Git作為儲存介質。
因此,我相信大部分讀者在生產上也是用Git作為儲存介質,搭配Spring-Cloud-Config使用。
但是呢,博主認為以Git作為儲存介質存在一些硬傷。
Git的許可權控制是個坑
Git的許可權管理是說控制使用者能不能Push或者Delete分支,或者能不能Push程式碼,而不是能不能訪問某個目錄的檔案。
對目錄和檔案的可讀是Git的最基本要求,不可能做到針對目錄級別的不可讀。
因此如果直接使用,會出現這樣一種情形
不同團隊之間可以互看對方配置!
於是,可能會有如下情形發生
A團隊同事A:"小B,這個地方不懂怎麼配?"
A團隊同事B:"去看看B團隊的配置,直接貼過來。"
然後B團隊就會發現自己的中介軟體裡總是會多出一些莫名奇妙的資料!
當然,你可以禁止研發直接登陸Git改配置。然後呢,基於Git研發一套配置管理系統,在上面做許可權控制,但是又有幾個公司這麼做呢?因為,這可能帶來第二個問題。
粒度問題
將配置資訊放在Git中,有一個致命問題: 粒度太粗了!
你每次對一條配置發生crud的操作,其帶來的影響是整個檔案發生變動。如果將來我們需要對某條配置做灰度釋出,基於Git來做是比較麻煩的,注意了,我沒說不能做,只是比較麻煩。
那麼,當時我們最理想的儲存介質就是資料庫,將配置資訊放在資料庫裡有 兩個好處
-
基於資料庫開發一套配置管理系統,顯然比基於git來開發容易的多!
-
將配置放在資料庫裡,每條配置對應資料庫的表中的一條記錄。這麼做粒度夠細,針對某些重要的配置,做灰度釋出,實現起來就容易很多。
因此,我們採用資料庫作為儲存介質。慶幸的是,這一點在Spring-Cloud-Config中是支援的。在該元件下,只需要設定
spring.profiles.active=jdbc
就能夠啟用jdbc模式。
但是我們很快發現了一個更大的問題,也正是因為這個問題,我們不得以需要進行改寫Spring-Cloud-Config。
Spring-Cloud-Config的重新整理機制是個坑!
因為一個配置中心應該要能夠做到,配置發生改動的時候,專案能夠自動感知,自動更新配置才對。在Spring-Cloud-Config中,這套機制是藉助一些程式碼倉庫(SVN、Github等)提供的Webhook機制加上Spring-Cloud-Bus來實現的。
在Webhook中配置一個回撥地址,重新整理流程如下圖所示
OK,那麼問題又來了!
(1) 配置資料放在資料庫中,資料庫裡沒有Webhook這種東西啊,怎麼做到實時重新整理?
(2) Spring-Cloud-Config的這套重新整理機制依賴於訊息匯流排,依賴於訊息佇列,存在延遲的情況!且依賴於訊息佇列的可用性,系統的複雜度大大增加。如果生產環境上訊息隊列出問題了,我們的重新整理功能就會受到影響!
所以,筆者認為這套重新整理機制並不是很盡如人意,需要進行修改。因此,我們很自然而然的想到了利用長輪詢來改寫Spring-Cloud-Config的重新整理機制!
長輪詢是什麼
既然有長輪詢,那必定有短輪詢,我順便講講短輪詢是什麼!
假設我們有一個需求
在頁面上要實時顯示後臺的庫存數量!比如庫存減少了,使用者不需要進行重新整理,頁面上的數字自己會變化。
那麼,如果採取短輪詢就是在客戶端(js)中不斷訪問後臺,後臺接到請求馬上返回最新的庫存數,然後重新整理到這個頁面當中。
短輪詢的缺點?
很明顯資源浪費。假設有幾百人打開了該頁面,就有幾百個請求在不停的請求服務端,明顯聽著就不合理。
因此,自然就有了長輪詢的出現!
其實也很簡單,客戶端(js)依然是不斷的去請求。但是呢,服務端不是馬上返回。而是等待庫存數量變化了再返回。大家知道,HTTP都有超時時間。如果在該時間內,依然沒有變化,客戶端將再次發起請求。
注意了,長短輪詢對於客戶端來說是沒有區別的,就是不斷的輪詢。但是對於服務端,區別就比較大了。在短輪詢情況下,服務端對於每次請求不管有沒有變化都會立即返回結果。而長輪詢情況下,如果有變化才會立即返回結果。而如果沒有變化,服務端則不會再立即給客戶端返回結果,直到超時為止。
怎麼實現
那麼,我們在專案中採用Spring的DeferredResult來實現。在Servlet3.0以後引入了非同步請求之後,Spring封裝了一下提供了相應的支援,也就是DeferredResult,能夠極大的提升吞吐量。
可能有人對Servlet的非同步化不熟,我大概介紹一下。我們平時常用的是同步Servlet,其執行流程如下圖所示
缺點很明顯啦 ,業務邏輯執行緒和servlet容器執行緒是同一個,一般的業務邏輯總得發生點IO,比如查詢資料庫,比如產生RPC呼叫,這個時候就會發生阻塞,而我們的servlet容器執行緒肯定是有限的,當servlet容器執行緒都被阻塞的時候我們的服務這個時候就會發生拒絕訪問,從而吞吐量上不去!
那麼,你使用非同步Servlet如下圖所示
在非同步Servlet中,業務執行緒有自己的執行緒池進行處理,並不會佔用Tomcat中的執行緒,從而提升了吞吐量!
那麼,怎麼利用DeferredResult怎麼實現長輪詢呢? 流程如下
(1)客戶端和服務端建立TCP連線
(2)客戶端發起HTTP請求
(3)服務端發起請求,監聽60s內是否有配置發生變動 (如何監聽配置發生變動?)
(4)如果沒發生變動,給客戶端返回304標誌位,客戶端繼續發起請求
(5)如果發生了變動,服務端會呼叫DeferredResult.setResult返回200狀態碼,客戶端收到響應結果後,會發起請求獲取變更後的配置資訊。
最後一個問題:如何有效快速的監聽出配置表的資料發生了變動?
因為我們用的是mysql。這裡有一個Mysql的自定義函式叫mysql-udf-http。具有http_get()、http_post()、http_put()、http_delete()四個函式,可以在MySQL資料庫中利用HTTP協議進行REST相關操作。
然後再和mysql的觸發器結合起來用,可以實現在配置表發生變動的時候,主動通知我們的配置中心服務端。讓服務端明白配置發生了變動!
一個疑問
採用長輪詢技術來實現配置重新整理,客戶端和服務端需之間需要一直保持TCP連線進行通訊。可能有些朋友會擔心,到底服務端能撐多少的連線?可能覺得對效能有影響?
這裡給出參考配置
使用了記憶體8G、4核的虛擬機器約可以撐8000左右的連線!
總結
最後這套配置中心,我基於原有的Spring-Cloud-Config,改寫其中的重新整理機制,更加符合我們的業務場景!現已將改寫思路說清,大家可以自行嘗試!