動態化方案通常都是比較大型的, 好比react native 、flutter 等都是從UI,運行邏輯等多方面完整的動態更新。但實際上,移動端還有不少細粒度的配置類數據須要支持動態更新的。 前端
好比某一個文案或者廣告的位置但願能夠根據用戶表現來隨時改動,又好比你開開發了一個線上功能,但上線後才發現裏面潛藏了一個嚴重的問題, 但願能夠同過一個線上開關當即關閉此功能。react
這一類需求用一句話來說就是叫作:千萬不要寫死。 git
Android裏面你們都很數據的sharedpreference,裏面能夠存儲不少的K-V類型的數據。那咱們如何來實現一套可動態更新的K-V存儲機制呢? 從而將這類小型配置或AB開關準時下發到因此的客戶端。算法
爲了實現這個功能,不少公司都研發了一套本身的配置中心繫統,好比阿里內部的orange系統,能狗作到秒級下發因此配置到全量客戶端。後端
要實現這樣一套系統,須要考慮到幾個問題點:緩存
首先咱們要解決的問題是:如何才能儘快告訴客戶端,配置數據以及更新了?安全
1.主動拉取(Pull)性能優化
第一種方式,咱們可讓客戶端進行主動輪詢:服務器
關於長輪詢說明下,普通的短鏈接是客戶端發起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編碼,把二進制數據轉成文本寫入配置文件中。
前面說了,咱們的配置中心可以作到秒級下發,那這裏會存在一個問題:若是配置有問題,可能會當即致使大規模線上故障。所以,這裏須要支持配置的灰度發佈。
通常能夠支持多維度灰度,好比基於用戶的App版本區間、Uid或者DeviceId、城市、渠道等維度信息來進行灰度。灰度發佈後開始觀察線上數據表現,肯定邏輯運行正常且穩定,若是沒問題,才能夠全量發佈。
那若是出了問題呢?那就要進行回滾。這裏咱們能夠經過從新發佈一個歷史版本的配置數據來實現回滾。
另外,全量發佈後,咱們會將更新的配置數據壓縮發佈到CDN。這時,大量客戶端在檢測到更新後,會當即訪問CDN下載對應的更新配置數據。若是用戶量級很大,須要考慮會出現的CDN請求高峯,要確保CDN能承受對應的訪問壓力,不能被打掛。並且,因爲CDN同步須要一段時間,因此此時確定會有不少CDN發生回源,對源服務器也會形成必定的壓力。所以,在每次發佈後,能夠結合具體場景及用戶QPS,有選擇地考慮對CDN進行預熱。
配置中心逐步成爲各家公司的標配,一套好的配置中心繫統,不只可以支持各類維度的配置下發,如業務配置、技術配置、開關配置等,也要可以作到實時下發,可以儘快觸達全部用戶。尤爲在一些用戶量較大的場景下,要可以考慮流量壓力,保證CDN等基礎設施可以穩定運行。