如何設計一個麻雀般的微型分佈式架構?

序言(初衷)

設計該系統初衷是基於描繪業務(或機器集羣)存儲模型,分析代理緩存服務器磁盤存儲與回源率的關係。系統意義是在騰訊雲成本優化過程中,量化指導機房設備擴容。前半部分是介紹背景,對CDN緩存模型做一些理論思考。後半部分會實際操作搭建一個微型但是五臟俱全的分佈式通用系統架構,最後賦予該系統一些跟背景相關的功能,解決成本優化中遇到的實際問題。

緩存服務器存儲模型架構(背景):

圖1 存儲模型

騰訊CDN的線上路由是用戶à分佈於各地區各運營商的OC->SOC->SMid->源站。各個層級節點部署的都是緩存服務器。來自用戶的部分請求流量命中服務器,另一部分產生回源流量。

隨着業務帶寬自然增長,用戶端帶寬增長,假設業務回源率不變的情況下,磁盤緩存淘汰更新(淘汰)速率變快,表現爲以下業務瓶頸(iowait變高、回源帶寬變高,由於磁盤空間大小受限的緩存淘汰導致回源率變高)。

爲了說明這個原理。我們假設兩個極端:一個是設備磁盤容量無限大,業務過來的流量緩存只受源站緩存規則受限。只要緩存沒過期,磁盤可以無限緩存,回源流量只需要首次訪問的流量,所以這個回源量(率)只跟業務特性(重複率)有關係。另一個極端是磁盤極限小(歸零),那麼無論業務設置緩存是否過期,客戶端訪問量都是1比1的回源量。假設業務平均的緩存週期是1個小時。那麼這1個小時的首次緩存帶寬(同一cache key的多次訪問,我們認爲是一次)將是這個硬盤的所需要的空間。這個大小是合理的,可以保證磁盤足夠容納業務的量。假設這個量達不到,或者本來達到了,但是由於業務自然增長了,1個小時內地首次緩存帶寬變多,硬盤空間也不夠用。

設備擴容是個解決辦法。但是壓測系統在這之前,沒有客觀數據證明需要擴容多大設備。或者擴容多少設備沒有進行灰度驗證,設備到位拍腦袋直接線上部署機器。我們在實驗機器進行線上日誌的重放,模擬出存儲模擬曲線,來指導線上機房合理的設備存儲。這就是建設重放日誌系統的意義。

麻雀雖小,五臟俱全的重放日誌模型(總覽)

這一章,我們定義了下列模塊:

模擬日誌服務器:下載線上某個機房的一段時間週期的訪問日誌。一個日誌存放10分鐘訪問記錄。機房有幾臺機器就下載幾份日誌。日誌服務器同時提供任務分片信息的查詢服務。假設我們需要重放任務id爲pig_120t的任務切片。下圖既爲任務切片詳情。

圖2 日誌服務器的日誌分片文件

任務控制器:啓動任務或者結束任務總開關。任務分配均勻分配給具體的肉雞和代理服務器。插入任務到Task Pool中,收集服務端的實時總流量、回源流量、總請求次數和回源次數數據並插入到回源率結果數據表。

肉雞:輪詢Task Pool的任務表。如果有任務,則按照任務明細(時間、線上機房ip)向日志服務器請求下載該分片的日誌。重放請求到指定的代理服務器。

代理服務端:提供實時回源數據查詢服務。並且安裝nws緩存服務器等組件,該機器等同於線上機房的軟件模塊。

實時展示界面:可隨時查看實時回源率和一些任務異常狀態信息。

圖3爲客戶端和服務端的互動圖。圖4是任務控制端在任務進行中和其他模塊的聯動過程。

圖3 肉雞和代理服務端的架構

圖4 控制端的任務聯動過程

分佈式系統特點

日誌重放模型核心是一個高性能壓測系統,但是需要添加一些邏輯:日誌下載、日誌分析重構、結果數據收集、數據上報展示。分佈式系統核心是:是否做到了可拓展、可恢復、簡易搭建、容錯、自動化。以下內容會一一展開。

先說說高性能:在一個通用模型中。我們模擬線上日誌,這個系統要做到高效、因爲我們的重放日誌速度要比線上的qps還要快。機器的重放速度決定了分析結果的速度。同時更快的速度,所需要的肉雞資源更少。筆者在python各個url請求庫和golang中,最終敲定使用了golang實現肉雞。golang做到了和原生c+epoll一樣快的速度,但是代碼實現容易多了。理論上我們對一臺做過代理端性能瓶頸分析。線上日誌比模擬日誌更復雜,qps適度下降是必然的。Golang這個客戶端達到預期目標。

可擴展:在我們可能會隨時增加模擬機器集羣的肉雞數量,或者更多的閒置代理服務器資源加入壓測任務。所以系統在可用機器數據表隨時加入新的機器。

圖5 系統的動態可擴展

可恢復:分佈式系統不同於單機模式。不能避免可能有各種故障,有時候系統部分節點出錯了,我們更傾向於不用這個節點,而不是繼續使用未處理完成的結果。即非0即1,無中間狀態。還有分佈式系統網絡傳輸延遲不可控。所以壓測系統設計了一套容錯機制:包括心跳檢測失敗,自動在數據表剔除肉雞服務端。接口異常容錯。超時過期未完成任務去除。crontab定時拉取退出進程等。

簡易搭建:使用ajs接口,和批處理安裝腳本。自動化部署肉雞和服務端。配置dns解析ip(日誌服務器,任務池、回源率結果所在的數據庫ip),tcp time_wait狀態的複用,千萬別忘了還有一些系統限制放開(放開ulimit fd limit,這裏設置100000,永久設置需要編輯/etc/security/limits.conf)。如果肉雞有依賴程序運行庫需要同時下載。在肉雞機器下載肉雞客戶端和配置、在服務端機器下載服務端和配置,下載定時拉起程序腳本,並添加到crontab定時執行。以上都用批處理腳本自動執行。

一些設計範式的思考

Single-productor and Multi-consumer

在肉雞客戶端的設計中:讀日誌文件一行一條記錄,添加到消息管道,然後多個執行worker從消息管道取url,執行模擬請求。消息管道傳送的是一條待執行的日誌url。IO消耗型程序指的是如果consumer執行訪問日誌並瞬間完成結果,但是productor需要對日誌進行復雜的字符串處理(例如正則之類的),那麼它下次取不到數據,就會被管道block住。另外一種是CPU消耗型程序,如果日誌url已經預先處理好了,productor只是簡單的copy數據給消息管道。而consumer訪問url,經過不可預知的網絡延遲。那麼多個consumer(因爲是包括網絡訪問時間,consumer個數設計超過cpu核數,比如2倍)同時訪問,讀端速度慢於寫端數度。在對一個日誌文件進行實驗,我們發現處理18w條記錄日誌的時間是0.3s,而執行完這些url的訪問任務則需要3分鐘。那麼很顯然這是一個CPU消耗性進程。如果是IO消耗型的程序。Golang有種叫fan out的消息模型。我們可以這樣設計:多個讀端去讀取多個chan list的chan,一個寫端寫一個chan。Fanout則將寫端的chan,循環寫到chan list的chan中。

Map-reduce

我們有時會做一個地理位置一個運營商的機房日誌分析。一個機房包含數臺機器ip。合理的調度多個肉雞客戶端並行訪問日誌,可以更快速得到合併回源率數據。

並行機制,經典的map-reduce,日誌文件按機房機器ip緯度切片分發任務,啓動N個肉雞同時並行訪問,等最後一臺肉雞完成任務時,歸併各個肉雞數據按成功請求數量、成功請求流量、失敗請求數量、失敗請求流量等方式做統計。同時用於和線上日誌做校樣。這裏的mapper就是肉雞,產生的數據表,我們按照關注的類型去提取就是reducer。

簡化的map-reducer(不基於分佈式文件系統),map和reduce中間的數據傳遞用數據表實現。每個mapper產生的日誌數據先放在本地,然後再上報給數據表。但是數據表大小的限制,我們只能上傳頭部訪問url。所以如果用這個辦法實現,數據是不完整的,或者不完全正確的數據。因爲也許兩臺肉雞合併的頭部數據正好就包括了某肉雞未上傳的日誌(該日誌因爲沒有到達單機肉雞訪問量top的標準)。

那麼如何解決這個問題呢,根本原因在於彙總數據所在的文件系統是本地的,不是分佈式的(hadoop的hdfs大概就是基於這種需求發明的把)。如果是狀態碼緯度,這種思路是沒問題的,因爲http狀態碼總量就那麼少。那麼如果是url緯度,比如說某機房給單肉雞的單次任務在10分鐘的url總數據量達到18萬條。只看日誌重複數>100的肉雞數據。這樣誤差最大值是100*肉雞數,所以對於10臺肉雞的機房,只要是綜合合併結果>1000。都是可信任的。如果是域名緯度,少數頭部客戶流量佔比大多數帶寬。 這也就是所謂的hot-key,少數的hot-key佔據了大多數比例的流量。所以域名緯度時,這個時候可以把關注點縮放在指定域名的url列表。如果本地上報給數據表的數據量太大,url也可以考慮進行短地址壓縮。當然如果不想彎道超車的話,需要硬解決這個問題,那可能得需要hdfs這種分佈式文件系統。

Stream-Processing

我們進行日誌客戶端系統,需要向日志服務器下載此次任務所需要的日誌(一般是一個機器10分鐘的訪問日誌)。首先本地日誌會去任務服務器查詢重放任務。接着去日誌服務器下載。如果該模擬集羣是在DC網絡組建,那麼下載一個10分鐘(約150M左右的文件)日誌幾乎在1兩秒內搞定,但是如果這個分佈式系統是組建於OC網絡,那麼OC網絡的肉雞服務器要去DC(考慮機房可靠性,日誌服務器架設在DC網絡)下載,經過nat轉化內網到外網,下載則需要10s左右。如果爲了等待日誌服務器下載完,也是一筆時間開銷。

在分佈式系統中,所謂的stream-processing,和batch processing不同的是,數據是無邊界的。你不知道什麼時候日誌下載完。而batch processing的前後流程關係,好比生產流水線的工序,前一道完成,後一道纔開始,對於後一道是完全知道前一道的輸出結果有多少。

所謂的流式處理則需要在前一道部分輸出結果到達時,啓動後一道工序,前一道工序繼續輸出,後一道則需要做出處理事件響應。後一道需要頻繁調度程序。

消息系統(message broker):前一道的部分輸出,輸入給消息系統。消息系統檢測到是完整的一條日誌,則可以產生後一道工序的輸入。這裏我們會碰到一個問題。下載日誌的速度(10s)會遠遠快於執行重放這些日誌的速度(3min)。按照一個消息系統可能的動作是:無buffer則丟棄,按照隊列緩存住,執行流控同步後一道工序和前一道工序的匹配速度。這裏我們選擇了按照隊列緩存住這個方案。當然在一個嚴謹的分佈式數據庫設計,message broker是一個能考率到數據丟失的節點。Broker會把完整數據發給後道工序,同時會把buffer數據緩存到硬盤備份,以防程序core dump。如果對於慢速前道工序,可以進行綜合方案配置,丟棄或者流控。這裏消息broker不同於數據庫,他的中間未處理數據是暫時存儲,處理過的消息要清除存儲。

總結

當然:現實中的生產線的分佈式系統會遠比這個複雜,但是本文實現的從0到1的迷你麻雀分佈式系統有一定的實踐意義。它不是一蹴而就的,不斷地版本迭代。當然該系統也完成了作者的kpi-存儲模型分析,在中途遇到問題時,進行的設計思考和改良,在此總結分享給大家。

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