學習筆記:The Log(我所讀過的最好的一篇分散式技術文章)

foreach_break發表於2015-07-17

前言
這是一篇學習筆記。學習的材料來自Jay Kreps的一篇講Log的博文。原文很長,但是我堅持看完了,收穫頗多,也深深為Jay哥的技術能力、架構能力和對於分散式系統的理解之深刻所折服。同時也因為某些理解和Jay哥觀點吻合而略沾沾自喜。

Jay Kreps是前Linkedin的Principal Staff Engineer,現任Confluent公司的聯合創始人和CEO,Kafka和Samza的主要作者。

所謂筆記,就是看了文章,提筆就記,因為Jay哥本身本章組織的太好,而其本身的科學素養及哲學素養也很高,所以私以為出彩的東西就不省略了。

一、資料來源
The Log: What every software engineer should know about real-time data’s unifying abstraction

二、筆記
2.1 Log的價值
1) Log是如下系統的核心:

  • 分散式圖資料庫
  • 分散式搜尋引擎
  • Hadoop
  • 第一代和第二代K-V資料庫

2) Log可能跟計算機的歷史一樣長,並且是分散式資料系統和實時計算系統的核心。
3) Log的名字很多:

  • Commit log
  • Transaction log
  • Write-ahead log

4) 不理解Log,你就不可能充分理解

  • 資料庫
  • NoSQL儲存
  • K-V儲存
  • 複製
  • Paxos演算法
  • Hadoop
  • Version Control
  • 或者,任何軟體系統

2.2 什麼是Log?
2.2.1 概述

  • 記錄會附加到log的尾部。
  • 從左到右讀取記錄。
  • 每個entry都有唯一且有序的log entry 序號。

記錄的順序定義了這樣的一個概念:時間。
因為越靠左的記錄越早。
Entry的序號可以當作一種時間戳,將記錄的順序當作時間這一概念看起來很奇怪,但是很快你就會發現,這樣做:可以方便地將“時間”與任一特定的物理時鐘解耦。
Log和常見的檔案、表(table)沒有那麼大的差別。

  • 檔案是一組位元組
  • 表是一組記錄
  • Log可以說是某種將記錄按時間排序的檔案或者表

這樣說,可能你會覺得log如此簡單,還有討論的必要嗎?
其實,log的核心意義在於:

Log記錄了何時發生了什麼(they record what happened and when.)。

而這一條,通常是分散式系統最最最核心的東西。
注意,這裡有必要澄清幾個概念:

  • 本篇所討論的Log和程式設計師通常接觸的應用日誌(application logs)不同
  • 應用日誌通常是一種非結構化的,記錄錯誤資訊、除錯資訊,用於追蹤應用的執行的,給人看的日誌,比如通過log4j或者 syslog來寫入本地檔案的日誌。
  • 而本篇所討論的log是通過程式設計方式訪問的,不是給人看的,比如“journal”、“data logs”。
  • 應用日誌是本篇所討論的log的一種特化。

2.2.2 資料庫中的Logs
Log的起源不得而知,就像發明二分查詢的人,難以意識到這種發明是一種發明。
Log的出現和IBM的System R 一樣早。
在資料庫中,需要在資料庫崩潰時,保持多種多樣的資料結構和索引保持同步。
為保證原子性和永續性,資料庫需要在對資料結構和索引進行修改提交之前,記錄其要修改的內容。
所以log記錄了何時發生了什麼,而每一張表和索引本身,都是這種歷史資訊的對映。
因為log是立即持久化的,所以當crash發生時,其成為恢復其它持久化結構的可靠來源。

Log從保證ACID特性的一種實現,發展成了一種資料庫之間資料複製的手段。

很顯然,資料庫中發生的一系列的資料變更,成為資料庫之間 保持同步最需要的資訊。
Oracle、MySQL、PostgreSQL,都包含了log傳輸協議,將log的一部分傳送到用於保持複製的從資料庫(Slave)。
Oracle的XStreams和GoldenState,將log當作一種通用的資料訂閱機制,以提供給非Oracle的資料庫訂閱資料。
MySQL和PostgreSQL也提供了類似的元件,這些元件是資料系統架構的核心。
面向機器的Log,不僅僅可被用在資料庫中,也可以用在:

  • 訊息系統
  • 資料流(data flow)
  • 實時計算

2.2.3 分散式系統中的logs
Log解決了兩個很重要的分散式資料系統中的問題:
1) 有序的資料變化
2) 資料分散式化

所謂的狀態機複製原理(State Machine Replication Principle):

如果兩個確定的處理過程,從相同的狀態開始,按照相同的順序,接收相同的輸入,那麼它們將會產生相同的輸出,並以 相同的狀態結束。

所謂確定的(deterministic),是指處理過程是時間無關的,其處理結果亦不受額外輸入的影響。
可以通過非確定的例子來理解:

  • 多執行緒的執行順序不同導致不同的結果
  • 執行getTimeOfDay()方法
  • 其它的不能重複的處理過程

所謂狀態,可以是機器上的任意資料,無論在處理結束後,是在機器的記憶體中還是磁碟上。
相同的輸入按照相同的順序,產生相同的結果,這一點值得引起你的注意,這也是為什麼log會如此重要,這是一個直覺性的概念:如果你將同一個log輸入兩個確定性的程式,它們將產生相同的輸出。
在分散式系統的構建中,意識到這一點,可以使得:
讓所有的機器做同樣的事,規約為:
構建分散式的、滿足一致性的log系統,以為所有處理系統提供輸入。

Log系統的作用,就是將所有的輸入流之上的不確定性驅散,確保所有的處理相同輸入的複製節點保持同步。

這種方法的最妙之處在於,你可以將索引日誌的時間戳,作為所有複製節點的時鐘來對待:

通過將複製節點所處理過的log中最大的時間戳,作為複製節點的唯一ID,這樣,時間戳結合log,就可以唯一地表達此節點的整個狀態。

應用這種方法的方式也很多:

  • 在log中記錄對一個服務的請求
  • 在回覆請求的前後,記錄服務狀態的變化
  • 或者,服務所執行的一系列轉換命令,等等。

理論上來講,我們可以記錄一系列的機器指令,或者所呼叫方法的名稱及引數,只要資料處理程式的行為相同,這些程式就可以保證跨節點的一致性。
常玩兒資料庫的人,會將邏輯日誌和物理日誌區分對待:

  • 物理日誌:記錄了所有的行內容的變化。
  • 邏輯日誌:不是記錄內容的變化,而是Insert , update , delete等導致行內容變化的SQL語句。

對分散式系統,通常有兩種方式來處理複製和資料處理:
1) State machine model(active – active)
2) Primary-back model (active – passive)

如下圖所示:

為了理解上述兩種方式的不同,來看個簡單的例子:
現在,叢集需要提供一個簡單的服務,來做加法、乘法等算術運算。初始,維護一個數字,比如0。

  • Active – active :在日誌記錄這樣的一些操作,如“+1”、“*2”等,這樣,每個複製節點需要執行這些操作,以保證最後的資料狀態是一致的。
  • Active – passive:一個單獨的master節點,執行“+1”、“*2”等操作,並且在日誌中記錄操作的結果,如“1”、“3”、“6”等。

上面的例子也揭示了,為什麼順序是複製節點之間保持一致性的關鍵因素,如果打亂了這些操作的順序,就會得到不同的運算結果。
分散式log,可以當做某些一致性演算法的資料結構:

  • Paxos
  • ZAB
  • RAFT
  • Viewstamped Replication

一條log,表徵了一系列的關於下一個值是什麼的決定。

2.2.4 Changelog
從資料庫的角度來看,一組記錄資料變化的changelog和表,是對偶和互通的。
1) 依據記錄了資料變化的log,可以重構某一狀態的表(也可以是非關係型儲存系統中有key的記錄)
2) 相反,表如果發生了變化,可以將變化計入log。

這正是你想要的準實時複製的祕籍所在!

這一點和版本控制所做的事情極為類似:管理分散式的、併發的、對狀態進行的修改。

版本控制工具,維護了反映修改的補丁,這其實就是log,你和一個被簽出(checked out)的分支快照進行互動,這份快照就相當於資料庫中的表。你會發現,版本控制與分散式系統中,複製都是基於log的:當你更新版本時,你只是拉取了反映了版本變化的補丁,並應用於當前的分支快照。

2.3 資料整合(Data integration)
2.3.1 資料整合的含義
所謂資料整合,就是將一個組織中的所有服務和系統的資料,變得可用。

實際上,對資料進行有效利用,很符合馬斯洛的層次需求理論。
金字塔的最底層,是收集資料,將其整合進應用系統中(無論是實時計算引擎,還是文字檔案,還是python指令碼)。
而這些資料,需要經過轉換,保持一個統一、規範、整潔的格式,以易於被讀取和處理。
當上面的要求被滿足後,就可以開始考慮多種多樣的資料處理方式,比如map – reduce 或者實時查詢系統。
很顯然,如果沒有一個可靠的、完備的資料流,Hadoop就僅僅是一個昂貴的、難以整合的加熱器(叢集很費電麼?)。
相反,如果能保證資料流可靠、可用且完備,就可以考慮更高階的玩法、更好的資料模型和一致的、更易被理解的語義。
接著,注意力就可以轉移到視覺化、報表、演算法和預測上來(挖啊機啊深度啊)。

2.3.2 資料整合的兩個複雜性

事件

事件資料,記錄了事件是怎麼發生的,而不僅僅是發生了什麼,這一類log通常被當做應用日誌,因為一般是由應用系統寫入的。但這一點,其實混淆了log的功能。
Google的財富,其實,是由一個建立在(使用者)點選流和好惡印象(體驗)之上的相關性pipeline產生的,而點選流和印象,就是事件。

各種各樣的專業資料系統的爆發

這些系統存在的原因:

  • 聯機分析(OLAP)
  • 搜尋
  • 簡單的線上儲存
  • 批處理
  • 圖譜分析
  • 等等(如spark)

顯然,要將資料整合進這樣的系統中,對於資料整合來講,極為困難。

2.3.3 基於日誌結構的資料流
每種邏輯意義上的資料來源,都可以依據log進行建模。

資料來源可以是記錄了事件(點選和PV)的應用程式,可以是接受更改的資料庫表。

每個訂閱者,都儘可能快地從這些資料來源產生的log中獲取新的記錄,應用於本地的儲存系統,並且提升其在log中的讀取偏移(offset)。訂閱者可以是任何資料系統,比如快取、Hadoop、另一個站點的資料庫,或者搜尋引擎。

Log,實際上提供了一種邏輯時鐘,針對資料變化,可以測量不同的訂閱者所處的狀態,因為這些訂閱者在log中的讀取偏移不同且相互獨立,這種偏移就像一個時間意義上的“時刻”一樣。

考慮這樣一個例子,一個資料庫,和一些快取伺服器:
Log提供了這樣一種能力,可以使得所有的快取伺服器得到同步,並推出它們所處的“時刻”。

假設我們寫入了一個編號為X的log,要從某個快取伺服器讀取資料,為了不讀到老資料,只需要保證:在快取伺服器將資料(同步)複製到X這個位置前,我們不從這個快取中讀取任何東西即可。

此外,log還提供了作為緩衝區的能力,以支援生產者和消費者的行為以非同步的方式進行。

最關鍵的一個支援非同步的原因,是訂閱系統可能會發生崩潰、因維護而下線,接著恢復上線,而在這種情況下,每個訂閱者都以自己的步調消費資料。

一個批處理系統,比如Hadoop,或者一個資料倉儲,是以小時或天為單位消費資料,而一個實時系統,通常在秒級消費資料。
而資料來源或者log,對消費資料的訂閱者一無所知,所以,需要在pipeline中做到無縫的新增訂閱者和移除訂閱者。

更重要的是,訂閱者,只需要知道log,而不需要對其所消費的資料的來源有任何瞭解,無論這個資料來源是RDBMS、Hadoop,還是一個最新流行的K-V資料庫,等等。

之所以討論log,而不是訊息系統,是因為不同的訊息系統所保證的特性不同,並且用訊息系統這個詞,難以全面和精確表達某種語義,因為訊息系統,更重要的在於重定向訊息。

但是,可以將log理解為這樣一種訊息系統,其提供了永續性保證及強有序的語義,在通訊系統中,這稱作原子廣播。

2.4 在Linkedin
Linkedin目前的主要系統包括(注:2013年):

  • Search
  • Social Graph
  • Voldemort (K-V儲存)
  • Espresso (文件儲存)
  • Recommendation engine
  • OLAP query engine
  • Hadoop
  • Terradata
  • Ingraphs (監控圖譜及metrics服務)

每個系統,都在其專業的領域提供專門的高階功能。

(這一段太長太長了,Jay兄十分能侃啊,所以挑重點的來記吧!)

1) 之所以引入資料流這個概念,是因為要在oracle資料庫的表之上,建立一個抽象的快取層,為搜尋引擎的索引構建和社交圖譜更新,提供擴充能力。

2) 為了更好的處理linkedin的一些推薦演算法,開始搭Hadoop叢集,但團隊在此塊的經驗尚淺,所以走了很多彎路。

3) 開始時,簡單粗暴地認為只要將資料從oracle資料倉儲中拉出來,丟進hadoop就可以了。結果發現:第一,將資料從oracle資料倉儲快速匯出是個噩夢;第二,也是更糟糕的一點,資料倉儲中某些資料的處理不對,導致了hadoop的批處理任務不能按預期輸出結果,且通過hadoop批處理執行任務,通常不可逆,特別是在出了報表之後。

4) 最後,團隊拋棄了從資料倉儲中出資料的方式,直接以資料庫和logs為資料來源。接著,造出了一個輪子:K-V 儲存(Voldemort)。

5) 即使是資料拷貝這樣不高大上的活兒,也佔據了團隊大量的時間去處理,更糟的是,一旦資料處理的pipeline中有個點出錯,hadoop立馬變得廢柴,因為再牛逼的演算法跑在錯誤的資料上,只有一個後果,就是產生更多的錯誤資料。

6) 即使團隊構建的東西抽象層次很高,針對每種資料來源還是需要特定的配置,而這也是很多錯誤和失敗的根源。

7) 一大批程式設計師想跟進,每個程式設計師都有一大批的想法,整合這個系統,新增這個功能,整合這個特色,或者想要自定義的資料來源。

8) Jay哥開始意識到:
第一, 雖然他們構建的pipelines還很糙,但是卻極其有價值。即使是解決了資料在新的系統(如hadoop)中可用的問題,也解鎖了一大批可能性。以前難做的計算開始變為可能。新的產品和分析,僅需要解鎖其它系統中的資料,並且進行整合,就可以容易地做出來。

第二, 很明顯,可靠地資料裝載需要更堅實的支撐,如果能夠捕獲所有的結構,就可以讓hadoop資料裝載完全自動化,不需要加入新的資料來源或人工修改資料的模式。資料會神奇地出現在HDFS中,而新的資料來源加入後,Hive的表會用合適的列自動化地、自適應地生成。

第三,資料覆蓋度遠遠不足。因為要處理很多新的資料來源,很難。

9) 為了解決新資料來源加入後的資料裝載問題,團隊開始了這樣的嘗試:

這裡寫圖片描述

很快,他們發現這樣搞行不通,因為釋出和訂閱、生產和消費,資料流通常還是雙向的,這成了一個O(n^2)的問題。
所以,他們需要的是這樣的模型:

需要將每個消費者從資料來源隔離,理想的情況下,這些消費者只和一個data repository進行互動,而這個repository可以提供它們訪問任意資料的能力。

10)訊息系統 + log = Kafka,kafka橫空出世。

2.5 Log和ETL、資料倉儲的關係
2.5.1 資料倉儲
1) 一個裝有乾淨的、結構化的、整合的資料repository,用於分析。
2) 雖然想法很美好,但是獲取資料的方式有點過時了:週期性地從資料庫獲取資料,將其轉換為某種可讀性更佳的格式。
3) 之前的資料倉儲問題在於:將乾淨的資料和資料倉儲高度耦合。

資料倉儲,應該是一組查詢功能的集合,這些功能服務於報表、搜尋、ad hot 分析,包含了計數(counting)、聚合(aggregation)、過濾(filtering)等操作,所以更應該是一個批處理系統。

但是將乾淨的資料和這樣的一種批處理系統高度耦合在一起,意味著這些資料不能被實時系統消費,比如搜尋引擎的索引構建、實時計算和實時監控系統,等等。

2.5.2 ETL
Jay哥認為,ETL無非做兩件事:

1) 對資料進行抽取和清洗,將資料從特定的系統中解鎖
2) 重構資料,使其能通過資料倉儲進行查詢。比如將資料型別變為適配某個關係型資料庫的型別,將模式轉換為星型或者雪花模式,或者將其分解為某種面向列的儲存格式。

但是,將這兩件事耦合在一起,問題很大,因為整合後的、乾淨的資料,本應能被其它實時系統、索引構建系統、低延時的處理系統消費。

資料倉儲團隊,負責收集和清洗資料,但是,這些資料的生產者往往因為不明確資料倉儲團隊的資料處理需求,導致輸出很難被抽取和清洗的資料。
同時,因為核心業務團隊對和公司的其它團隊保持步調一致這件事兒不敏感,所以真正能處理的資料覆蓋度很低,資料流很脆弱,很難快速應對變化。

所以,更好的方式是:

如果想在一個乾淨的資料集上做點搜尋、實時監控趨勢圖、實時報警的事兒,以原有的資料倉儲或者hadoop叢集來作為基礎設施,都是不合適的。更糟的是,ETL所構建的針對資料倉儲的資料載入系統,對其它(實時)系統點兒用沒有。

最好的模型,就是在資料釋出者釋出資料之前,就已經完成了資料的清洗過程,因為只有釋出者最清楚它們的資料是什麼樣的。而所有在這個階段所做的操作,都應該滿足無損和可逆。

所有豐富語義、或新增值的實時轉換,都應在原始的log釋出後處理(post-processing),包括為事件資料建立會話,或者新增某些感興趣的欄位。原始的log依舊可被單獨使用,但是此類實時應用也派生了新的引數化的log。

最後,只有對應於具體的目標系統的資料聚合操作,應作為資料裝載的一部分,比如轉換為星型或雪花型模式,以在資料倉儲中進行分析和出報表。因為這個階段,就像傳統的ETL所做的那樣,因為有了非常乾淨和規範的資料流,(有了log後)現在變得非常簡單。

2.6 Log檔案和事件
以log為核心的架構,還有個額外的好處,就是易於實現無耦合的、事件驅動的系統。

傳統的 捕獲使用者活動和系統變化的方式,是將此類資訊寫入文字日誌,然後抽取到資料倉儲或者hadoop叢集中進行聚合和處理,這個問題和前面所述的資料倉儲和ETL問題類似:資料與資料倉儲的高度耦合。

在Linkedin,其基於kafka構建了事件資料處理系統。為各種各樣的action定義了成百上千種事件型別,從PV、使用者對於廣告的趕腳(ad impressions)、搜尋,到服務的呼叫和應用的異常,等等。

為了體會上述事件驅動系統的好處,看一個簡單的關於事件的例子:
在工作機會頁面上,提供一個機會。這個頁面應該只負責如何展示機會,而不應該過多地包含其它邏輯。但是,你會發現,在一個具有相當規模的網站中,做這件事,很容易就會讓越來越多的與展示機會無關的邏輯牽扯進來。

比如,我們希望整合以下系統功能:
1) 我們需要將資料傳送到hadoop和資料倉儲做離線處理。
2) 我們需要統計頁面瀏覽次數,以確保某些瀏覽不是為了抓取網頁內容什麼的。
3) 我們需要聚合對此頁面的瀏覽資訊,在機會發布者的分析頁面上呈現。
4) 我們需要記錄某使用者對此頁面的瀏覽記錄,以確保我們對此使用者提供了有價值的、體驗良好的任何適宜此使用者的工作機會,而不是對此使用者一遍又一遍地重複展示某個機會(想想老婆不在家才能玩的遊戲吧,那紅綠藍閃爍的特效,配合那勁爆的DJ風舞曲,或者那搖擺聚焦的事業峰和齊X小短裙的girls,然後點進去才發現是標題黨的ad吧!)。
5) 我們的推薦系統需要記錄對此頁面的瀏覽記錄,以正確地追蹤此工作機會的流行度。

很快,僅僅展示機會的頁面邏輯,就會變得複雜。當我們在移動端也增加了此機會的展示時,不得不把邏輯也遷移過去,這又加劇了複雜程度。還沒完,糾結的東西是,負責處理此頁面的工程師,需要有其它系統的知識,以確保上述的那些功能能正確的整合在一起。

這只是個極其簡單的例子,在實踐中,情況只會更加複雜。
事件驅動可以讓這件事變得簡單。

負責呈現機會的頁面,只需要呈現機會並記錄一些和呈現相關的因素,比如工作機會的相關屬性,誰瀏覽了這個頁面,以及其它的有用的與呈現相關的資訊。頁面不需要保持對其它系統的知識和了解,比如推薦系統、安全系統、機會發布者的分析系統,還有資料倉儲,所有的這些系統只需要作為訂閱者,訂閱這個事件,然後獨立地進行它們各自的處理即可,而呈現機會的頁面不需要因為新的訂閱者或消費者的加入而做出修改。

2.7 構建可擴充套件的log
分離釋出者和訂閱者不新鮮,但是要保證多個訂閱者能夠實時處理訊息,並且同時保證擴充套件能力,對於log系統來說,是一件比較困難的事。

如果log的構建不具備快速、低開銷和可擴充套件能力,那麼建立在此log系統之上的一切美好都免談。

很多人可能認為log系統在分散式系統中是個很慢、重型開銷的活兒,並且僅用來處理一些類似於ZooKeeper更適合處理的後設資料等資訊。

但是Linkedin現在(注:2013年),在kafka中每天處理600億條不同的訊息寫入(如果算資料中心的映象的話,那就是幾千億條寫入)。

Jay哥他們怎麼做到的呢?

1) 對log進行分割(partitioning the log)
2) 通過批量讀寫優化吞吐量
3) 避免不必要的資料拷貝

通過將log切為多個partition來提供擴充套件能力:

1) 每個partition都是有序的log,但是partitions之間沒有全域性的順序。

2) 將訊息寫入哪個partition完全由寫入者控制,通過依照某種型別的key(如user_id)進行分割。

3) 分割使得log的附加操作,可以不用在分片(sharding)之間進行協調就進行,同時,保證系統的吞吐量和kafka叢集的規模呈線性關係。

4) 雖然沒有提供全域性順序(實際上消費者或者訂閱者成千上萬,討論它們的全域性順序一般沒有啥價值),但是kafka提供了這樣一種保證:傳送者按照什麼順序將訊息發給某個partition,從這個partition遞交出去的訊息就是什麼順序(什麼順序進,什麼順序出)。

5) 每個partition都按照配置好的數目進行復制,如果一個leader節點掛了,其它的節點會成為新的leader。

6) 一條log,同檔案系統一樣,線性的讀寫模式可被優化,將小的讀寫log可以組成更大的、高吞吐量的操作。Kafka在這件事上做的很猛。批處理用在了各種場景之下,比如客戶端將資料傳送到服務端、將資料寫入磁碟、伺服器之間的資料複製、將資料傳送給消費者,以及確認提交資料等場景。

7) 最後,kafka在記憶體log、磁碟log、網路中傳送的log上,採用了很簡單的二進位制格式,以利於利用各種優化技術,比如零拷貝資料傳輸技術(zero-copy data transfer)。

諸多的優化技術,匯聚起來,可以讓你即使在記憶體爆滿的情形下,也能按照磁碟或網路能提供的最大能力進行資料讀寫。

2.8 Logs和實時處理
你以為Jay哥提供了這麼個美麗的方法把資料複製來複制去就完了?
你!錯!了!

Log是流的另一種說法,logs是流處理的核心。

2.8.1 什麼是流處理
Jay哥認為:
1)流處理是連續資料處理的基礎設施。
2)流處理的計算模型,就如同MapReduce或其它分散式處理框架一樣,只是需要保證低延遲。
3)批處理式的收集資料模式,導致了批處理式的資料處理模式。
4)連續的收集資料模式,導致了連續的資料處理模式。
5)Jay哥講了個美國人口普查的方式來解釋批處理。

在linkedin,無論是活動資料還是資料庫的變化,都是連續的。
批處理按天處理資料,和連續計算將視窗設為一天雷同。

所以,流處理是這樣一種過程:
6)在處理資料時,帶了一個時間的概念,不需要對資料保持一個靜態的快照,所以可以在使用者自定義的頻率之下,輸出結果,而不必等資料集到達某種“結束”的狀態。
7)從這個意義上講,流處理是批處理的一種泛化,並且考慮到實時資料的流行程度,這是一種極其重要的泛化。
8)許多商業公司無法建立流處理引擎,往往因為無法建立流資料收集引擎。
9)流處理跨越了實時響應式服務和離線批處理的基礎設施之間的鴻溝。
10)Log系統,解決了很多流處理模式中的關鍵問題,其中最大的一個問題就是如何在實時的多個訂閱者模式下,提供可用資料的問題(流資料收集)。

2.9 資料流圖譜
流處理中最有趣的地方在於,其擴充了什麼是資料來源(feeds)這一概念。
無論是原始資料的logs、feeds,還是事件、一行一行的資料記錄,都來自應用程式的活動。
但是,流處理還可以讓我們處理來自其它feeds的資料,這些資料和原始資料,在消費者看來,並無二致,而這些派生的feeds可以包含任意程度的複雜性。

一個流處理任務,應該是這樣的:從logs讀取資料,將輸出寫入logs或者其它系統。

作為輸入和輸出的logs,連通這些處理本身,和其它的處理過程,構成了一個圖。

事實上,以log為核心的系統,允許你將公司或機構中的資料捕獲、轉換以及資料流,看作是一系列的logs及在其上進行寫入的處理過程的結合。

一個流處理程式,其實不必很高大上:可以是一個處理過程或者一組處理過程,但是,為了便於管理處理所用的程式碼,可以提供一些額外的基礎設施和支援。

引入logs有兩個目的:

1) 保證了資料集可以支援多個訂閱者模式,及有序。
2) 可以作為應用的緩衝區。這點很重要,在非同步的資料處理程式中,如果上游的生產者出資料的速度更快,消費者的速度跟不上,這種情況下,要麼使處理程式阻塞,要麼引入緩衝區,要麼丟棄資料。
丟棄資料似乎不是個好的選擇,而阻塞處理程式,會使得所有的資料流的處理圖譜中的處理程式卡住。而log,是一種很大,特大,非常大的緩衝區,它允許處理程式的重啟,使得某個程式失敗後,不影響流處理圖譜中的其它程式。這對於一個龐大的機構去擴充套件資料流是非常關鍵的,因為不同的團隊有不同的處理任務,顯然不能因為某個任務發生錯誤,整個流處理程式都被卡住。

Storm和Samza就是這樣的流處理引擎,並且都能用kafka或其它類似的系統作為它們的log系統。

(注:Jay哥相當猛,前有kafka,後有samza。)

2.10 有狀態的實時處理
很多流處理引擎是無狀態的、一次一記錄的形式,但很多用例都需要在流處理的某個大小的時間視窗內進行復雜的counts , aggregations和joins操作。
比如,點選流中,join使用者資訊。

那麼,這種用例,就需要狀態的支援。在處理資料的地方,需要維護某個資料的狀態。

問題在於,如何在處理者可能掛掉的情況下保持正確的狀態?

將狀態維護在記憶體中可能是最簡單的,但抵不住crash。

如果僅在某個時間視窗內維護狀態,當掛掉或者失敗發生,那麼處理可以直接回退到視窗的起點來重放,但是,如果這個視窗有1小時那麼長,這可能行不通。

還有個簡單的辦法,就是把狀態存在某個遠端的儲存系統或資料庫中,但是這會損失資料的區域性性併產生很多的網路間資料往返(network round-trip)。

回憶下,上文中曾提到的資料庫中的表和log的對偶性。
一個流處理元件,可以使用本地的儲存或索引來維護狀態:

  • Bdb
  • Leveldb
  • Lucene
  • Fastbit

通過記錄關於本地索引的changelog,用於在crash後恢復狀態。這種機制,其實也揭示了一種一般化的,可以儲存為任意索引型別的,與輸入流同時被分割(co-partitioned)的狀態。

當處理程式崩潰,其可以從changelog中恢復索引,log充當了將本地狀態轉化為某種基於時間備份的增量記錄的角色。

這種機制還提供了一種很優雅的能力:處理過程本身的狀態也可以作為log被記錄下來,顯然,其它的處理過程可以訂閱這個狀態。

結合資料庫中的log技術,針對資料整合這一場景,往往可以做出很強大的事:

將log從資料庫中抽取出來,並在各種各樣的流處理系統中進行索引,那麼,與不同的事件流進行join就成為可能。

2.11 Log 合併
顯然,用log記錄全時全量的狀態變更資訊,不太可能。

Kafka使用了log合併或者log垃圾回收技術:

1) 對於事件資料,kafka只保留一個時間視窗(可在時間上配置為幾天,或者按空間來配置)
2) 對於keyed update,kafka採用壓縮技術。此類log,可以用來在另外的系統中通過重放技術來重建源系統的狀態。

如果保持全時全量的logs,隨著時間增長,資料將會變得越來越大,重放的過程也會越來越長。
Kafka不是簡單地丟棄老的日誌資訊,而是採用合併的方式,丟棄廢棄的記錄,比如,某個訊息的主鍵最近被更新了。

2.12 系統構建
2.12.1 分散式系統
Log,在分散式資料庫的資料流系統和資料整合中所扮演的角色是一致的:

  • 抽象資料流
  • 保持資料一致性
  • 提供資料恢復能力

你可以將整個機構中的應用系統和資料流,看作是一個單獨的分散式資料庫。
將面向查詢的獨立系統,比如Redis , SOLR , Hive tables 等等,看作是一種特別的、資料之上的索引。
將Storm、Samza等流處理系統,看做一種精心設計過的觸發器或者物化檢視機制。

各式各樣的資料系統,爆發性的出現,其實,這種複雜性早已存在。
在關係型資料庫的輝煌時期(heyday),某個公司或者機構光關係型資料庫就有很多種。

顯然,不可能將所有的東西都丟進一個Hadoop叢集中,期望其解決所有的問題。所以,如何構建一個好的系統,可能會像下面這樣:

構建一個分散式系統,每個元件都是一些很小的叢集,每個叢集不一定能完整提供安全性、效能隔離、或者良好的擴充套件性,但是,每個問題都能得到(專業地)解決。

Jay哥覺得,之所以各式各樣的系統爆發性地出現,就是因為要構建一個強大的分散式系統十分困難。而如果將用例限制到一些簡單的,比如查詢這樣的場景下,每個系統都有足夠的能力去解決問題,但是要把這些系統整合起來,很難。

Jay哥覺得在未來構建系統這事兒有三種可能:

1) 保持現狀。這種情況下,資料整合依然是最頭大的問題,所以一個外部的log系統就很重要(kafka!)
2) 出現一個強大的(如同輝煌時期的關係型資料庫)能解決所有問題的系統,這似乎有點不可能發生。
3) 新生代的系統大部分都開源,這揭示了第三種可能:資料基礎設施可被離散為一組服務、以及面向應用的系統API,各類服務各司其事,每個都不完整,卻能專業滴解決專門的問題,其實通過現存的java技術棧就能看出端倪:

  • ZooKeeper:解決分散式系統的同步、協作問題(也可能受益於更高抽象層次的元件如helix、curator).
  • Mesos、YARN:解決虛擬化和資源管理問題。
  • 嵌入式的元件Lucene、LevelDB:解決索引問題。
  • Netty、Jetty及更高抽象層次的Finagle、rest.li解決遠端通訊問題。
  • Avro、Protocol Buffers、Thrift及umpteen zlin:解決序列化問題。
  • Kafka、bookeeper:提供backing log能力。

從某種角度來看,構建這樣的分散式系統,就像某個版本的樂高積木一樣。這顯然跟更關心API的終端使用者沒有太大關係,但是這揭示了構建一個強大系統並保持簡單性的一條道路:
顯然,如果構建一個分散式系統的時間從幾年降到幾周,那麼構建一個獨立的龐大系統的複雜性就會消失,而這種情況的出現,一定是因為出現了更可靠、更靈活的“積木”。

2.12.2 Log在系統構建中的地位
如果一個系統,有了外部log系統的支援,那麼每個獨立的系統就可以通過共享log來降低其自身的複雜性,Jay哥認為log的作用是:

1) 處理資料一致性問題。無論是立即一致性還是最終一致性,都可以通過序列化對於節點的併發操作來達到。

2) 在節點間提供資料複製。

3) 提供“提交”的語義。比如,在你認為你的寫操作不會丟失的情況下進行操作確認。

4) 提供外部系統可訂閱的資料來源(feeds)。

5) 當節點因失敗而丟失資料時,提供恢復的能力,或者重新構建新的複製節點。

6) 處理節點間的負載均衡。

以上,大概是一個完整的分散式系統中應提供的大部分功能了(Jay哥確實愛Log!),剩下的就是客戶端的API和諸如一些構建索引的事了,比如全文索引需要獲取所有的partitions,而針對主鍵的查詢,只需要在某個partition中獲取資料。

(那把剩下的事情也交代下吧,Jay哥威武!)

系統可被分為兩個邏輯元件(這強大的理解和功力):

1) Log層
2) 服務層

Log層,以序列化的、有序的方式捕獲狀態的變化,而服務層,則儲存外部查詢需要的索引,比如一個K-V儲存可能需要B-tree、sstable索引,而一個搜尋服務需要倒排索引。

寫操作既可以直接入log層,也可以通過服務層做代理。寫入log會產生一個邏輯上的時間戳(log的索引),比如一個數字ID,如果系統partition化了,那麼,服務層和log層會擁有相同的partitions(但其各自的機器數可能不同)。

服務層訂閱到log層,並且以最快的速度、按log儲存的順序追log,將資料和狀態變化同步進自己的本地索引中。

客戶端將會得到read-your-write的語義:

通過對任一一個節點,在查詢時攜帶其寫入時的時間戳,服務層的節點收到此查詢,通過和其本地索引比較時間戳,如果必要,為了防止返回過期的老資料,推遲請求的執行,直到此服務節點的索引同步跟上了時間戳。

服務層的節點,也許需要、也許不需要知道leader的概念。在很多簡單的用例中,服務層可不構建leader節點,因為log就是事實的來源。

還有一個問題,如何處理節點失敗後的恢復問題。可以這樣做,在log中保留一個固定大小的時間視窗,同時對資料維護快照。也可以讓log保留資料的全量備份並使用log合併技術完成log自身的垃圾回收。這種方法,將服務層的眾多複雜性移至log層,因為服務層是系統相關(system-specific)的,而log層確可以通用。

基於log系統,可以提供一組完備的、供開發使用的、可作為其它系統的ETL資料來源、並供其它系統訂閱的API。

Full Stack !:

顯然,一個以log為核心的分散式系統,其本身立即成為了可對其它系統提供資料裝載支援及資料流處理的角色。同樣的,一個流處理系統,也可以同時消費多個資料流,並通過對這些資料流進行索引然後輸出的另一個系統,來對外提供服務。

基於log層和服務層來構建系統,使得查詢相關的因素與系統的可用性、一致性等因素解耦。

也許很多人認為在log中維護資料的單獨備份,特別是做全量資料拷貝太浪費、太奢侈,但事實並非如此:

1) linkedin(注:2013年)的kafka生產叢集維護了每資料中心75TB的資料,而應用叢集需要的儲存空間和儲存條件(SSD+更多的記憶體)比kafka叢集要高。
2) 全文搜尋的索引,最好全部裝入記憶體,而logs因為都是線性讀寫,所以可以利用廉價的大容量磁碟。
3) 因為kafka叢集實際運作在多個訂閱者的模式之下,多個系統消費資料,所以log叢集的開銷被攤還了。
4) 所有以上原因,導致基於外部log系統(kafka或者類似系統)的開銷變得非常小。

2.13 結語
Jay哥在最後,不僅厚道地留下了很多學術、工程上的有價值的論文和參考連結,還很謙遜地留下了這句話:

If you made it this far you know most of what I know about logs.

終。

相關文章