前端開發都應該知道的配置中心

前端開發都應該知道的配置中心

動態化方案一般都是比較大型的, 比如react native 、flutter 等都是從UI,運行邏輯等多方面完整的動態更新。但實際上,移動端還有很多細粒度的配置類數據需要支持動態更新的。

比如某一個文案或者廣告的位置希望可以根據用戶表現來隨時改動,又比如你開開發了一個線上功能,但上線後才發現裏面潛藏了一個嚴重的問題, 希望可以同過一個線上開關立即關閉此功能。

這一類需求用一句話來講就是叫做:千萬不要寫死。

Android裏面大家都很數據的sharedpreference,裏面可以存儲很多的K-V類型的數據。那我們如何來實現一套可動態更新的K-V存儲機制呢? 從而將這類小型配置或AB開關準時下發到所以的客戶端。

爲了實現這個功能,很多公司都研發了一套自己的配置中心繫統,比如阿里內部的orange系統,能狗做到秒級下發所以配置到全量客戶端。

要實現這樣一套系統,需要考慮到幾個問題點:

  • 首先,遠端配置更新後,如何實時通知給所有客戶端呢?
  • 配置數據體積越來越大,下載失敗率變高,如何優化數據包大小呢?
  • 配置數據的安全性如何保證?
  • 如何灰度發佈配置?
  • 實時全量下發的話,CDN會存在什麼問題嗎?
  • Android 如何實現多進程監聽配置變更?

如何通知客戶端配置更新

首先我們要解決的問題是:如何才能儘快告訴客戶端,配置數據以及更新了?

1.主動拉取(Pull)

第一種方式,我們可以讓客戶端進行主動輪詢:

  • Polling定時輪詢:定時發起請求到後端,拉取最新的配置數據。。
  • Long Polling長輪詢:後臺起一個service,持續去發去長輪詢判斷後端是否有配置數據更新,一旦發現有更新,再發起請求去拉取配置

關於長輪詢說明下,普通的短連接是客戶端發起socket請求,服務端收到後,不管有沒有數據,都會立即返回。

而如果長輪詢,服務端如果沒有數據則會hold該請求,將socket等請求信息保存起來, 不立即返回, 等到有數據時才通過socket重新寫回客戶端。當客戶端收到結果,或者判斷請求超時,便會發起下一次的長輪詢請求, 此時的超時時間一般會比正常的http請求的超時時間長, 比如一分鐘,比減少請求次數。

這種方式可能能夠達到比較好的實時性,但很顯然,會帶來非常多的流量浪費,而且對後端也會帶來非常多無用的請求,造成機器資源的浪費。

2.推送(Push)

這種方式可以藉助長連接

當我們發佈更新配置後,可以通過長連接通道向在線用戶下發配置變更通知, 用戶收到通知後便會主動去拉取配置。

這種方式可以極大的降低流量損耗,只有配置變更時纔會拉取數據,但也存在一些問題:

* 需要維護穩定長連接通道,存在一定的技術成本。
* 如果用戶長連接斷開,會導致無法接收到消息,從而不能保證實時變更。

因此,這種非方式雖然節省流量,但不能夠保證100%的實時配置生效率。

3.推拉結合

既然主動拉取和推送都做不到,很自然的,我們會想到能不能兩種方式結合起來,方案如下:

* 客戶端與服務端保持一個長連接,當有配置變更時,立即下發;
* 爲防止長連失效導致更新不及時,客戶端還會作定期輪詢,拉取配置;

通過這種方式,可以獲得較低的流量開銷和較高的更新率。業界不少企業採用的是類似的方案,比如攜程的配置中心繫統Apollo。

4.統一網關

這裏介紹第四種更新機制,來自阿里的Orange移動配置系統,它無需任何輪詢請求和長連接通道下發,而且可以做到在線用戶100%的秒級更新率。

它的祕密就是利用了全集團的統一網關,這套網關同時運行在客戶端(Android、iOS)和服務端。移動端網關會接管所有客戶端的請求,而後端網關則會承接所有移動端發過來的請求。具體流程如下:

* 客戶端的任何網絡請求在經過移動端網關時,帶上本地配置的版本號;
* 服務端網關收到請求後,抽取出配置版本號,發送給配置中心服務,其他參數透傳給業務後端
* 配置中心服務基於當前APP的版本號,配置版本號信息,判斷是否有新配置,如果有則返回新配置版本號給網關
* 當業務後端返回時,帶上這個新配置版本號給客戶端
* 客戶端發現新配置版本號,則去CDN拉取最新配置數據,完成更新。

在這個流程裏,我們沒有爲配置單獨發起輪詢請求,也不需要依賴長連接服務進行下發,而且藉助現有的業務數據,加上網關的協議,來實現配置的動態更新。只要客戶端處於活躍狀態,就能立即發現配置更細你, 而且非常穩定。

配置文件的增量更新,壓縮與加密

很多開發者擔心自己開發的功能上線後悔出現問,所以會加上各種各樣的配置開關,隨着版本的不斷迭代,配置數據肯定會越來越大,如果每次數據更新,都需啊喲去全量下載,那消耗的流量會越來越多,而且,數據包越大,下載更新失敗率會也會逐步變高。

因爲,我們還要想辦法減少下載配置數據包大小,一般會從兩個角度考慮

1.增量更新
2.壓縮

除此之外,有些配置數據是敏感的,所以應該要實現加密。

增量更新

增量算法有很多,我們可以結合具體場景來選擇。

由於我們的配置都是純文本,所以可以優先考慮文本Diff算法,如Google的diff-match-patch,就是專門針對純文本的高性能Diff/Patch算法。而且它提供了多種語言版本實現,包括Java、Objective-C、Python、Dart等,

diff-match-patch內部是基於Myers算法,這個算法就是我們天天用的 git diff 和 RecyclerView 裏的 DiffUtil的實現原理。而且diff-match-patch在這個基礎上還做了不少性能優化。

當然,除了純文本Diff,我們也可以考慮二進制Diff算法,如BsDiff算法,一般apk的更新、Tinker補丁包更新都可以採用這個增量算法。

壓縮

對於文本壓縮,用的最多的就是Gzip了,Http協議傳輸也是用的Gzip壓縮,兼具壓縮率和性能。所以此處也可以考慮Gzip壓縮,接入成本最低,效果也不錯。另外也可以考慮zStd壓縮和Brotli壓縮,感興趣的讀者可以去深入瞭解下。騰訊雲CDN服務目前已經支持了Gzip壓縮和Brotli壓縮,當文本文件大小在256Byte - 2048KB之間時,採用Gzip壓縮;更大的文件則採用Brotli壓縮。

加密

不少情況下,我們會用配置中心來下發一些敏感數據。比如雙十一活動,某個優惠券的生效時間戳,如果用戶分析出來了,可以人爲修改配置數據從而導致程序運行異常。因此,爲了保護下發的敏感配置數據,我們需進行加密。

第一種方式,下發鏈路加密。我們可以對下發的配置數據壓縮包進行整體加密,客戶端拿到後,流式解密和解壓到本地文件。以後客戶端讀取配置時可以直接讀取明文。這種方式的問題就是配置數據是以明文形式落盤(存儲到磁盤)的,這可能存在數據泄漏風險。

第二種方式,單Value加密。我們會對每個Key對應的Value進行加密,然後下發到客戶端,解壓後存儲在磁盤裏的是明文Key+密文Value,在需要讀取某個配置時,再實時解密並緩存在內存裏,從而降低數據泄漏風險。

在選擇具體加密方式方面,考慮到客戶端的解密耗時,可以使用類似AES/CBC/PKCS7Padding加密算法,密鑰可以存儲到客戶端保護起來(爲了安全可以放到so 內部,通過混淆提高安全性,或者採用白盒加密方案,將密鑰與算法一起編譯生成代碼從而隱藏密鑰),也可以支持動態更新。

另外,Value加密後是二進制數據,此時要利用Base64編碼,把二進制數據轉成文本寫入配置文件中。

灰度、回滾及CDN壓力

前面說了,我們的配置中心能夠做到秒級下發,那這裏會存在一個問題:如果配置有問題,可能會立即導致大規模線上故障。因此,這裏需要支持配置的灰度發佈。

一般可以支持多維度灰度,比如基於用戶的App版本區間、Uid或者DeviceId、城市、渠道等維度信息來進行灰度。灰度發佈後開始觀察線上數據表現,確定邏輯運行正常且穩定,如果沒問題,纔可以全量發佈。

那如果出了問題呢?那就要進行回滾。這裏我們可以通過重新發佈一個歷史版本的配置數據來實現回滾。

另外,全量發佈後,我們會將更新的配置數據壓縮發佈到CDN。這時,大量客戶端在檢測到更新後,會立即訪問CDN下載對應的更新配置數據。如果用戶量級很大,需要考慮會出現的CDN請求高峯,要確保CDN能承受對應的訪問壓力,不能被打掛。而且,由於CDN同步需要一段時間,所以此時肯定會有很多CDN發生回源,對源服務器也會造成一定的壓力。因此,在每次發佈後,可以結合具體場景及用戶QPS,有選擇地考慮對CDN進行預熱。

小結

配置中心逐步成爲各家公司的標配,一套好的配置中心繫統,不僅能夠支持各種維度的配置下發,如業務配置、技術配置、開關配置等,也要能夠做到實時下發,能夠儘快觸達所有用戶。尤其在一些用戶量較大的場景下,要能夠考慮流量壓力,保證CDN等基礎設施能夠穩定運行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章