基於Go語言構建的萬億級流量大數據平臺架構

黨合萱
黨合萱
碩士畢業於西安電子科技大學,曾就任於阿里雲存儲部門,主要從事存儲服務相關功能的設計與開發工做。於2016年加入七牛雲,主要負責流式計算與離線計算服務pipeline的架構和開發工做。目前pipeline承載公司天天超過千億、超過百TB的數據處理。


今天的分享主要圍繞七牛在最近一年時間裏面開發的大數據平臺進行展開,目前咱們的平臺已經承載了公司核心業務的運營;關於咱們的產品,主要會從一個場景展開進行介紹,當中包含了咱們在設計過程當中遇到的挑戰以及解決方案。也歡迎你們基於這些問題和咱們展開交流與討論。api

場景.產品

對於運維人員來講,在進行每平常規的線上運維時,日誌當中的一天內訪問量的波動、線上錯誤分佈、其餘業務指標這些數據對於運維人員來講並不是是一個透明的過程,那麼如何將這些東西作到可視化,或是將這些數據收集起來作統一的處理分析,實際上是一個比較複雜和較難實現的過程。這就是所謂的運維日誌分析,也是咱們以前所說起的場景。關於咱們產品解決場景的細節,在下面將會進一步進行分析。咱們以Nginx-log爲例對咱們的Pandora產品進行敘述。七牛雲存儲

數據接入Pandora—logkit配置運行markdown

任何數據分析的第一步都是數據接入。Pandora開發的數據接入工具logkit,能夠幫助用戶將數據打入Pandora平臺內;在最開始須要下載logkit,配置並運行(圖1)網絡

圖1
圖1

logkit工具支持多種數據源,好比對Nginx-log、kafka數據進行採集,並打入咱們的數據處理平臺當中。下面對圖 1 進行詳解,首先,咱們須要查看日誌格式,包括日誌格式的名稱。在圖 1 中,咱們明確了日誌存儲的路徑以及格式。最後進入配置文件,將須要進行配置的信息進行配置,並指明數據須要打入存放的路徑,若是須要打到某一個消息隊列中時,須要對密鑰進行配置並運行它,那麼此時這個數據纔會採集收錄到咱們的平臺當中。架構

日誌檢索併發

圖2
圖2

圖 2 所示是一個比較直觀的可視化界面,它支持拖拽,頁面左側能夠看到「數據源」與「日誌檢索」這兩項內容,配置好的 logkit 運行以後,全部數據都會打入「數據源」中。頁面右側則顯示了數據源中每一個字段的名稱、格式等信息。框架

圖3
圖3

圖 3 所示是「日誌檢索」的內容顯示頁面,經過「日誌檢索」咱們能夠清晰的查看一些業務邏輯,在搜索框中填入你的查詢條件,就能夠進行全文檢索,當須要查看過去某個時刻響應超過3s的全部請求,那麼經過「日誌檢索」頁面也能夠清楚的查詢並顯示出來。圖 3 僅僅是展現了一個全文搜索的狀態,在功能頁面還能夠查看相關數據分佈的柱狀圖。運維

日誌聚合工具

圖4
圖4

如圖 4 所示,打入到數據源裏面的數據,能夠經過一段SQL以每分鐘爲粒度進行計算聚合。能夠作聚合的內容不少,如來自某個IP的請求數量,也能夠是別的一些相關操做,聚合結束以後,數據便會再次迴流到咱們的數據源當中。簡單來講,咱們經過一次計算將數據從新迴流到數據源用於下一環節的分析處理,計算、迴流的過程是能夠不斷去進行級聯的,能夠實現不少相對比較複雜的數據處理。佈局

數據迴流至平臺

圖5
圖5

上面說起的數據迴流至數據源是一種處理方式,用戶搭建本身的一套HTTP服務,數據經過HTTP的接口迴流至其本身系統內是另一種數據處理方式。經過這種方式迴流的數據,用戶能夠將分析結果在本身平臺進行沉澱,操做頁面如圖 5 所示。

實時數據展現與監控

圖6
圖6

圖 6 所示直觀展示了咱們的監控頁面,監控服務須要開通以後再進行Grafana頁面配置,頁面的基本配置在咱們官方文檔中都有提供,用戶能夠直接下載導入。

圖7
圖7

圖 7 展現的是對 Nginx 的日誌進行分析以後的數據展現圖。左上角橙色的框(visits爲0)顯示可總訪問量,右上角綠色的柱狀圖則是在過去一段時間內發生的請求數以及響應時間,右下角的餅狀圖顯示了相關用戶訪問的佔比量。這些圖的樣式及位置均可以進行配置。

架構設計

圖8
圖8

圖 8 所示展現了Pandora的業務架構。數據經過Portal/Logkit/SDK/API能夠導入咱們的平臺,進入消息隊列當中,消息隊列當中的數據能夠通過計算反覆在計算任務和消息隊列之間進行流動,固然,這些數據也能夠直接導出。導出後的數據通過下游系統(日誌檢索/時序數據等)處理最終能夠生成數據報表,以上就是數據的整個流向。

Pipeline設計目標及技術選型

每一個系統在最初設計時都會擬定設計目標以及相應的須要解決的問題。下面先講一下咱們的設計目標,首先這個系統必須支持數據快速接入、高吞吐量、低延遲;其次做爲一個雲服務,它必須支持海量用戶併發訪問以及必須支持海量消息隊列;要提供實時計算與離線計算的框架知足計算需求;最終它必須是可視化的操做知足用戶操做需求。在設計目標提出以後,咱們要對選型進行規劃,咱們須要選擇具有高吞吐量的存儲系統,固然目前七牛的存儲系統無疑是最知足需求的;其次咱們須要強大靈活的大數據處理引擎;最後開發人員必須保證最終設計的產品是能夠快速迭代開發的。基於這些要求,咱們很輕易選擇了相應的技術支撐,使用Kafka來知足咱們的對海量消息隊列設計的需求;使用Spark做爲計算引擎;語言選型上則選用咱們底蘊積澱深厚的Golang,最終,在肯定這幾種技術選型以後,咱們便開始搭建系統。

圖9
圖9

圖 9 所示,是咱們Pipeline的總體架構設計,它負責pandora中數據的接入和處理。數據經過Logkit等方式導入到數據接入層,也就是apiserver。經過apiserver的數據會進入到消息隊列裏面,以後經過計算引擎的讀取和回寫操做,最終導入到下游系統中(LogDB/TSDB/HTTP/七牛雲存儲)咱們今天着重關注綠色箭頭指引的數據流方向,會說起裏面相關的重點進行詳解。在整個數據流流動過程當中,有幾個因素可能會決定這個系統的效率,好比穩定性、性能等。因此我將從用戶到消息隊列,通過計算任務再返回到消息隊列,最終導出數據這整個過程來說解。

數據接入層

圖10
圖10

圖 10 所示顯示的是數據接入層。數據經過apiserver導入,調度器用來管理一些用戶消息隊列的源數據,其中包括數據以何種形式寫入到消息隊列當中去。logkit這個工具之因此放在這裏,不是由於數據會經過apisever流向logkit最終再流向消息隊列,而是由於它能夠採集各類形式的數據,在這裏咱們用它採集系統審計日誌與監控信息。它很容易進行管理和配置。

容器化

圖11
圖11

在最開始設計這個系統時,擴容是一個比較困擾咱們的問題。由於接入的基本是內部用戶,接入速度比較快,因此一週以內須要擴容至少一到兩次,這在運維上是一個比較重的負擔。以後咱們採用了容器的方案,由於整個數據接入層是一個無狀態的組件,因此咱們將它容器化,使用咱們的容器雲產品解決。如圖 11 所示,每個pod中,咱們都將apisever與logkit佈局在一塊兒,經過監控數據,咱們將每一個容器包括這個集羣總體的信息所有都彙總在了這個調度器當中。調度器裏面承載着整個集羣負載及資源總量這些信息,能夠及時根據這些信息動態的實現擴容縮容。

數據寫入優化

圖12
圖12

圖 12 所示是對數據寫入進行優化的過程。第一代數據寫入流程,採用了串行的方式進行,數據導入以後是一行一行進行解析,所有解析以後再將數據寫入到消息隊列當中,可是這種方式的處理效率是很是低效的。因此咱們利用go語言的特性,採用了line channel,數據源源不斷進入channel,而後會在channel下游起多個parser,並行的對數據進行解析。也就是說咱們利用channel將處理變成了併發的過程,最終提升了CPU的利用率,下降了用戶響應的延遲率,大大優化了性能。

計算

圖13
圖13

如圖 13 所示,咱們的計算基於Spark 實現,提供了一個比較簡單的SQL,對用戶屏蔽了底層細節。

導出優化

圖14
圖14

數據流入整個系統中,在系統中無論是作計算仍是存儲,這些通過處理的數據若是須要發揮做用,都要流入到下游系統中,因此「導出數據」這個過程起到的是一個鏈接上下游,承上啓下的做用。圖 14 是這個系統的總架構圖,由於當時並未對導出服務作細粒度的任務切分,而且單臺server也處理不了過大的用戶任務,因此在高峯期時,會致使延遲增大,基於此,咱們通過一個月的開發最終推出了一個全新的版本。

圖15
圖15

如圖 15 所示,是通過改進後的總體架構圖。圖的頂層是咱們的master,用它來控制全部任務的調度管理。全部任務都是經由調度器轉發給master,由master來評估每一臺機器上的負載,以後再根據機器自己的一些狀態(CPU使用率、網絡帶寬、執行任務的狀況)去作相應的調度,除此以外咱們還將任務作了更細粒度的切分。

調度方法的設計首要考慮到的就是面向資源,其次須要充分利用異構機器,而且能知足自動調整。面向資源你們都可以理解,充分利用異構的機器,是由於咱們機器規格衆多,所能解決的任務強度不一致,咱們須要充分利用該機器的資源,而不能讓其在處理任務時,有"機器資源"不足或浪費的狀況發生;至於自動調整,就是能夠保證在面對用戶量突增或者突減這種突發狀況發生時,咱們具有自動調整任務分佈的能力,其最終的目的也是爲了充分利用資源。

任務分配

圖16
圖16

圖 16 是任務分配的過程圖。假設最初任務(T1-T7)都相對均勻的分佈在三臺機器上,此時又有另外兩個任務(T8-T9)進入,那麼咱們就須要尋找一些相對比較空閒的機器(S1 或 S2)優先將這兩個任務分配給他們。這只是針對一個相對比較均衡的狀況作的一個調整。

自動調整

圖17
圖17

圖18
圖18

固然也會有不均衡的狀況產生(圖 17-18)那麼此時就須要咱們去作一些自動調整,好比有一個用戶刪除了其不少任務,那麼此時的S1與S2相對S3會比較空閒,那麼此時咱們就須要經過server向master上報心跳,這個內容包括對資源的佔用以及任務的分佈狀況,根據結果對比較空閒的機器作一個調整,保持一個相對平衡的狀態。

水平擴展

圖19
圖19

圖 19 是進行水平擴展時會產生的一個問題。全部機器目前都處於一個比較繁忙的狀態,此時若是過來一個新的任務(T13),可是前12個任務已經所有分佈在這三臺機器上面處理,騰不出空閒的機器處理新增任務,那麼此時就會須要對機器進行擴容。

圖20
圖20

如圖 20 所示,在前三臺機器都處於「忙碌」狀態時,咱們須要新增server4,一旦啓動S4,它會向master彙報心跳,而後master就會感知到這個任務的存在以及S4的存在,從新對整個資源分佈使用狀況作一次評估,將T13分配到比較空閒的S4上,甚至能夠將在S一、S二、S3等待處理的任務分配到S4上。

資源隔離

圖21
圖21

實際上,不僅僅是對任務進行自動調整均衡分擔機器處理壓力是很是重要的,對於一些比較特殊的任務,如何保證這個用戶流量在突增時不會影響到其餘相對較小的用戶,或是當數據導出到雲存儲進行壓縮時(壓縮的過程很是耗費CPU資源)如何保證它不會影響其餘任務,這些都是咱們須要處理的問題。針對這些問題咱們提出了資源隔離的概念(圖 21)將機器和任務進行隔離,提供調度組(調度組中是相近的一組機器或者是一類任務)功能,經過對他們物理上的隔離,達到相互之間互不影響,並對資源進行充分利用。

master高可用

圖22
圖22

圖23
圖23

綜上咱們能夠看出咱們的系統是一對多的狀態(一個master對多個server)那麼在這種狀況下,如何解決在出現單點故障時仍然保證服務的高可用。如圖22到圖23所示,是咱們設計的一個核心所在,咱們能夠在圖中看到最底端是一個zookeeper集羣,咱們經過對一個臨時文件的建立來模擬一個鎖,多臺機器能夠同時去搶佔這把鎖,搶佔成功的master會成爲一個主master,沒有搶佔成功的則會做爲一個備份,在平時會空閒,但一旦S1丟鎖,master2就會搶佔鎖,接過整個調度任務以及一些集羣管理任務,這就是master高可用思路。

Server高可用

圖24
圖24

server高可用,咱們也是採用相似的思路。咱們將master視做一個高可用節點,每個server都須要向master彙報心跳,心跳的內容包含了機器自己的存活以及相應任務的執行狀況。如圖 24 所示,master一旦感知到S3宕機,那麼此時就會將S3上執行的兩個任務(T5-T6)都調走,而且它會認爲S1與S2是相對比較合適的選擇,而且會將這兩個任務調去相應的server上,這樣就完成了server的高可用目標。

系統級水平擴展

圖25
圖25

圖26
圖26

最開始有說起咱們的整個消息隊列是使用kafka實現的,kafka其實也是有上限的,在最開始咱們也是採用了kafka單個集羣(圖 25)後來發現,一旦業務量上來,消息隊列數據一旦多到必定程度,系統會發生雪崩。因此咱們對單個集羣作了一個擴展(圖 26)將單個kafka集羣直接拆成多個集羣,讓每個kafka集羣都保持一個相對比較小的規模,這樣性能方面就會獲得很大的提高,圖 26 所示就是通過擴展後的狀況,由三個kafka提供的信息會彙總到咱們的調度器上,調度器經過壓力或者是消息隊列的數量,對用戶新建立的任務以及新的數據源進行分配,分配至合適的kafka集羣中。

上下游協議優化

圖27
圖27

在實踐中仍是會出現上下游之間性能較低的狀況。在最開始,咱們採用Json來作上下游的數據傳輸,但是在日誌檢索時暴露出的問題就是,這樣作對網絡消耗很大,因而咱們決定採用Protobuf進行上下游數據傳輸。圖 27 展現了使用Json與Protobuf時,從序列化、反序列化角度進行對比的數據結果展現,從圖中能夠看出,使用Protobuf消耗的時間都更短,尤爲在反序列化時,它的CPU消耗下降了將近一個數量級。所以,採用這種方式,無論從集羣計算資源利用仍是從網絡帶寬提高上都將效率提高了數倍。

流水線處理

圖28
圖28

至於對流水線的處理,最開始的設計實際上是一個串行的操做,導出服務從消息隊列當中拉取數據,通過處理以後作一個推送,持續這樣的工做過程,其中處理操做是很快的,可是拉取和推送相對就很慢,這樣的一個過程,執行效率實際上是很低的,而且因爲每種操做的處理時間不同,有的快有的慢,就致使在監控圖上監察到網絡的趨勢圖是時高時低的,也就致使了利用率的下降。鑑於此,咱們優化了流水線操做,採用並行化操做(圖 28)結果顯示,這樣作的結果是推送和拉取效率都會比上面一種方式高。

Golang GC

咱們的整個語言選型是採用Golang的,而它是一個帶GC的語言,實際上出現的狀況仍是不少的,系統當中會有1/10的時間是不幹活而在進行垃圾回收的。所以在代碼層面咱們作了一些改進,一個是sync.Pool的使用可以下降垃圾回收的頻率;其次是重用對象,將一個對象儘量重複使用,這樣一來,每一次GC的量就會減少。以後咱們對Golang進行了版本升級,升級至1.8版本後咱們再查看了一下GC的耗時,發現提高了將近兩個數量級。這就是代碼層面的優化。

有限資源假設

最後敘述一下咱們關於資源假設方面進行的一個優化,也就是要創建起一個有限資源假設的概念。前段時間因爲數據接入量比較大,咱們須要本身進行運維,突發接入的客戶,會使系統輕易就被跑滿,此時咱們會想辦法加機器或者是說在調度上去作一些調整和優化,可是這樣終究不是一個長久的辦法。因而咱們會在剛開始去作一個資源有限假設,就是要在最開始評估出來資源有限狀況下咱們該如何去作。好比須要提早預估出10M帶寬所能對應多少用戶的任務,這個必須是有一個數據存在的,而且在這個基礎上咱們須要作一個資源的預估和集羣資源的規劃。根據這個預估的數據的狀況,去劃定一個水位標準,超過水位標準以後,再考慮是否是須要進行擴容。客戶這邊也須要溝通清楚咱們現有的處理能力,這樣,才能保證整個集羣/服務是處於一個比較健康的狀態。

成果

上面咱們說起了咱們的架構實現以及總體的一個優化,目前的成果就是:咱們支撐了萬億級的數據點,而且天天能夠處理幾百TB的數據,以及支持海量用戶。咱們的系統目前保持很低的延遲,較高的處理效率;因爲咱們實現了自動化運維,因此人力成本也大大減小,咱們的指望就是能夠在寫代碼時不被運維事情干擾;至於可用性,目前已經達到3個9(99.9%)的成效。

相關文章
相關標籤/搜索