1. 程式人生 > >如何設計一個微型分散式架構?

如何設計一個微型分散式架構?

序言(初衷)

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

快取伺服器儲存模型架構(背景):

img

圖1 儲存模型

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

隨著業務頻寬自然增長,使用者端頻寬增長,假設業務回源率不變的情況下,磁碟快取淘汰更新(淘汰)速率變快,表現為以下業務瓶頸(iowait變高、回源頻寬變高,由於磁碟空間大小受限的快取淘汰導致回源率變高)。

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

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

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

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

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

img

圖2 日誌伺服器的日誌分片檔案

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

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

代理服務端:提供實時回源資料查詢服務。並且安裝nws快取伺服器等元件,該機器等同於線上機房的軟體模組。

實時展示介面:可隨時檢視實時回源率和一些任務異常狀態資訊。

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

img

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

img

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

分散式系統特點

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

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

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

img

圖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-儲存模型分析,在中途遇到問題時,進行的設計思考和改良,在此總結分享給大家。

在此我向大家推薦一個我的架構學習交流群。 交流學習群號: 897889510

裡面會分享一些資深架構師錄製的視訊錄影:有Spring,c,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化、分散式架構等這些成為架構師必備的知識體系。