為什麼不用原生Spring-Cloud-Config

為什麼不用原生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中配置一個回調地址,刷新流程如下圖所示


為什麼不用原生Spring-Cloud-Config


OK,那麼問題又來了!

(1)配置數據放在數據庫中,數據庫裡沒有Webhook這種東西啊,怎麼做到實時刷新?

(2)Spring-Cloud-Config的這套刷新機制依賴於消息總線,依賴於消息隊列,存在延遲的情況!且依賴於消息隊列的可用性,系統的複雜度大大增加。如果生產環境上消息隊列出問題了,我們的刷新功能就會受到影響!

所以,筆者認為這套刷新機制並不是很盡如人意,需要進行修改。因此,我們很自然而然的想到了利用長輪詢來改寫Spring-Cloud-Config的刷新機制!

長輪詢是什麼

既然有長輪詢,那必定有短輪詢,我順便講講短輪詢是什麼!

假設我們有一個需求

在頁面上要實時顯示後臺的庫存數量!比如庫存減少了,用戶不需要進行刷新,頁面上的數字自己會變化。

那麼,如果採取短輪詢就是在客戶端(js)中不斷訪問後臺,後臺接到請求馬上返回最新的庫存數,然後刷新到這個頁面當中。

短輪詢的缺點?

很明顯資源浪費。假設有幾百人打開了該頁面,就有幾百個請求在不停的請求服務端,明顯聽著就不合理。

因此,自然就有了長輪詢的出現!

其實也很簡單,客戶端(js)依然是不斷的去請求。但是呢,服務端不是馬上返回。而是等待庫存數量變化了再返回。大家知道,HTTP都有超時時間。如果在該時間內,依然沒有變化,客戶端將再次發起請求。

注意了,長短輪詢對於客戶端來說是沒有區別的,就是不斷的輪詢。但是對於服務端,區別就比較大了。在短輪詢情況下,服務端對於每次請求不管有沒有變化都會立即返回結果。而長輪詢情況下,如果有變化才會立即返回結果。而如果沒有變化,服務端則不會再立即給客戶端返回結果,直到超時為止。

怎麼實現

那麼,我們在項目中採用Spring的DeferredResult來實現。在Servlet3.0以後引入了異步請求之後,Spring封裝了一下提供了相應的支持,也就是DeferredResult,能夠極大的提升吞吐量。

可能有人對Servlet的異步化不熟,我大概介紹一下。我們平時常用的是同步Servlet,其執行流程如下圖所示


為什麼不用原生Spring-Cloud-Config


缺點很明顯啦 ,業務邏輯線程和servlet容器線程是同一個,一般的業務邏輯總得發生點IO,比如查詢數據庫,比如產生RPC調用,這個時候就會發生阻塞,而我們的servlet容器線程肯定是有限的,當servlet容器線程都被阻塞的時候我們的服務這個時候就會發生拒絕訪問,從而吞吐量上不去!

那麼,你使用異步Servlet如下圖所示


為什麼不用原生Spring-Cloud-Config


在異步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,改寫其中的刷新機制,更加符合我們的業務場景!現已將改寫思路說清,大家可以自行嘗試!


作者:孤獨煙
原文:轉載自公眾號,孤獨煙,已獲作者授權


分享到:


相關文章: