作為大型復雜分布式系統(tǒng),微博平臺中存在大量的配置信息,這些配置信息定義了平臺中的RPC服務(wù)和資源(memcached、redis、mysql等)的地址,以及這些服務(wù)和資源的元數(shù)據(jù)信息。
在微博早期,配置信息散落在工程的代碼中,這種方式簡單方便,但是微博平臺系統(tǒng)規(guī)模擴大和業(yè)務(wù)部署復雜度的提升帶來了一些問題:
為了解決以上問題,微博平臺對配置做了一次大規(guī)模調(diào)整,將代碼和配置分離,配置按照機房、業(yè)務(wù)池以及環(huán)境拆分,并且存儲在本地文件系統(tǒng)中約定的位置。分離之后,各業(yè)務(wù)池、各機房的配置更加靈活,也為配置向 config service遷移提供了條件。
隨著微博平臺服務(wù)器規(guī)模的持續(xù)增長,RPC服務(wù)和資源的故障和變更已經(jīng)變?yōu)橐环N常態(tài),在日常維護過程中,經(jīng)常會遇到以下場景:
而現(xiàn)有的解決方案無法解決這些問題,我們迫切需要一個分布式環(huán)境下的持久配置系統(tǒng),提供高效的配置注冊獲取和及時變更通知服務(wù),于是vintage(config service在微博內(nèi)部的代號)應運而生。
Vintage是典型的基于pub-sub模型的通訊框架,在業(yè)界類似的系統(tǒng)很多,比如淘寶的diamond和軟負載中心,zookeeper在某些系統(tǒng)中(如kafka)也扮演著類似的角色。
Vintage 將信息集中管理在云端,并且提供實時的變更通知。Vintage主要應用于兩種主要場景:配置管理和命名管理,配置管理主要維護靜態(tài)配置數(shù)據(jù)(如服務(wù)超時時間、降級開關(guān)狀態(tài));命名管理主要維護RPC服務(wù)地址信息。兩種場景看起來極為相似,但是也存在如下差別:
Vintage在微博平臺內(nèi)部已經(jīng)獲得了很大的推廣和使用,RPC service、cache service、分布式trace系統(tǒng)、queue service都在一定程度上依賴vintage,它的重要程度是不言而喻的。 那么如此重要的服務(wù)是如何設(shè)計的呢?
Vintage在設(shè)計之初就明確了以下的目標:
vintage選擇redis作為配置信息的落地存儲,結(jié)構(gòu)簡單,無單點,具體如下圖所示:
vintage以高可用性為設(shè)計根本,我們在容災機制上投入了很大的精力。配置數(shù)據(jù)存儲在redis、服務(wù)端緩存、客戶端snapshot文件中,在多種故障場景下,vintage均能輕松應付:
由此可知,只有在服務(wù)端redis、tomcat全部不可用,客戶端的snapshot文件被刪除或者損壞的情況下,客戶端應用才會無法從vintage獲取配置信息,導致服務(wù)不可用,這種場景出現(xiàn)的幾率極低。
vintage相比于本地文件系統(tǒng)保存配置信息最大的優(yōu)勢就在于可以對配置變更的實時通知。實現(xiàn)變更通知最直觀的做法就是客戶端定期查詢配置信息,但是這種方式存在很多問題。
首先,配置信息在redis中以hash格式存儲,對信息的每次全量獲取都是一次hgetall操作,這個操作會極大的增加redis負載,降低吞吐量。
其次,vintage對應的客戶端很多,其本身存儲的數(shù)據(jù)量也很大,大量的全量獲取會壓滿網(wǎng)卡。
vintage的解決方案是這樣的:
vintage中除了存儲配置信息以外還會存儲信息的MD5碼,同時配置信息和MD5碼也會緩存在客戶端??蛻舳藨冒l(fā)起的每次查詢操作實際上只是查詢客戶端緩存中信息。而vintage client會定期的訪問server獲得最新的MD5碼與本地緩存的MD5比對,如果發(fā)生變化才會從server獲得最新的信息,并且緩存在客戶端緩存和snapshot文件中。具體流程如下圖所示:
之前提到,vintage的命名管理功能主要維護的是RPC服務(wù)的地址信息,這些信息是有生命周期的,生命周期狀態(tài)有三個:working、unreachable和removed,狀態(tài)變更圖如下:
其中,服務(wù)生命周期中的 working態(tài)和unreachable態(tài)的變化是通過RPC server和config service之間的心跳完成的,心跳時間戳也是存儲在redis中。每臺應用服務(wù)器中存在心跳接收器(heartbeat acceptor)異步寫入心跳時間戳,同時存在一個心跳處理器(heartbeat processor)定期的檢查心跳是否出現(xiàn)連續(xù)缺失。具體流程如下圖所示:
心跳服務(wù)對于命名服務(wù)來說至關(guān)重要,直接影響著服務(wù)的生命周期變化,在實際應用過程中,需要注意一下幾點:
vintage在設(shè)計上非常簡單,代碼量也不是很大,但是在實際使用中還是遇到各種各樣的問題,以下針對幾個典型問題和大家分享以下。
vintage接受來自所管理的RPC服務(wù)的心跳信息,并且根據(jù)配置的策略判斷RPC服務(wù)是否存活。那么在不考慮心跳包在傳輸過程中被篡改的前提下(Byzantine Generals Problem),只會出現(xiàn)兩種情況:
如果第二種情況發(fā)生會產(chǎn)生非常嚴重的后果,極端情況下會vintage會摘除所有的RPC服務(wù)節(jié)點,導致服務(wù)整體不可用。負載中心類型的產(chǎn)品通常都會遇到這樣的問題,vintage是如何解決的呢?
首先,vintage不會摘除心跳檢測中標志死亡的節(jié)點,只是把它標記為不可達的狀態(tài)(unreachable);而對于不可達節(jié)點的使用,vintage client中提供多種策略,由應用方來選擇是否使用以及如何使用不可達節(jié)點。
其次,vintage會保護服務(wù)節(jié)點,控制心跳檢測中標示不可達節(jié)點的數(shù)量,避免某一個服務(wù)集群的所有節(jié)點被標志為不可達。
之前提到引用方使用的信息實際上時存儲在vintage client的本地緩存中,那么當應用方重啟(如進行上線)或者信息發(fā)生變更時,vintage client的本地緩存失效,大量的請求會穿透到vintage server以及redis中,造成系統(tǒng)瞬時負載和網(wǎng)卡流量的暴漲,我們稱之為變更“風暴”。變更風暴對于vintage來說是致命的,因為應用方只有在此時會和vintage server交互,如果vintage server crash,會造成對vintage強依賴的應用方永遠無法啟動。
我們的應對方案如下:
vintage在微博系統(tǒng)架構(gòu)層面發(fā)揮的作用是不言而喻的,它增強了微博架構(gòu)的魯棒性,使各個業(yè)務(wù)系統(tǒng)可以進行快速地變更、降級、切換流量,它是微博架構(gòu)從稚嫩走向成熟的標示。
感謝崔康對本文的審校。