我在大約六年前的一個較爲巧合的時機加入了領英。當時咱們正面臨着單機應用,集中式數據庫帶來的挑戰,並開始將其遷移成一組定製的分佈式系統。這是一段頗有趣的經歷:咱們構建,編譯並運行了一套分佈式圖形數據庫,一個分佈式的搜索後臺應用,一套Hadoop和一代與二代鍵值數據庫。python
在此期間,我體驗最深的一件事情是,這些工具的本質是日誌。這些日誌能夠是預寫式日誌(write-ahead logs),能夠是提交日誌或是事物日誌。日誌基本是全部分佈式數據系統和實時應用架構的核心。web
你很難脫離日誌體系來透徹的瞭解關係型數據庫,非關係型數據庫,鍵值數據庫,備份,paxos,hadoop,版本控制或是任何軟件系統。可是,仍然有很多軟件工程師對日誌並不熟悉。我但願可以改變這一點。在這篇文章中,我會帶你學習日誌所必須知道的一切,包括什麼是日誌以及如何使用日誌進行數據集成,實時數據處理和系統構建。算法
日誌多是最簡單的存儲抽象形式。它只支持添加式寫入,徹底時間有序。數據庫
日誌被添加到圖片的末尾,而且按照從左往右的順序讀取。每一條日誌有惟一的順序的日誌編號。緩存
日誌記錄的順序隱藏了時間的屬性,由於左邊的日誌默認要「老於」右邊的日誌。每條日誌的編號能夠視做日誌的時間戳。從時間的角度來形容日誌編號乍一看有點奇怪,可是它使得日誌和任何物理時鐘節耦。在分佈式系統中,該屬性是相當重要的。服務器
日誌條目的內容和格式在本文彙總並不重要。一樣,咱們也不能無限的往日誌中新增內容,由於它會佔據全部的空間。稍後我會回來討論這一點。數據結構
因此,日誌並不是徹底不一樣與文件或是表格。文件是一組二進制編碼的,而表格是一組記錄,而日誌就是一種內容按照時間排序的表格或是文件。架構
至此你可能會想這麼簡單的東西有啥值得說的?一個只支持添加式的記錄和文件系統有關係?答案是日誌有一個特殊的目的:它們記錄發生的事件及其發生的時間。從各個角度看來,這都是分佈式文件系統中不少問題的本質所在。併發
但在深刻解釋以前,讓我先將一些容易混淆的概念解釋一下。每一個開發者都熟悉日誌的另外一個維度的定義:即記錄一個應用的無結構的錯誤信息或是錯誤路徑,並使用log4j或是syslog寫入本地文件系統中。應用日誌是本文中描述的日誌概念的降級形式。兩者之間最大的區別在於文本日誌主要是爲了開發者來閱讀,而本文描述的系統/數據日誌主要是由程序來訪問並解析。異步
(事實上,若是仔細想想,人肉閱讀單機上的日誌的想法並不高明。當不少服務參與進來,這種方式很快變得難以管理,而日誌就變成了打印入參出參以瞭解跨機器調用行爲的工具。此時文本式的日誌並非最合適的形式)
我不知道日誌的概念起源於何處--也許就和二分查找法同樣,由於過於簡單而並無被髮明者視爲是一個創舉。早在IBM的R系統中它就已經存在了。數據庫中使用日誌實如今應用崩潰時可以同步各種數據和索引。爲了保證操做的原子性和持久性,數據庫會在執行具體的變動以前,將須要修改的數據先寫入日誌中。日誌記錄了發生的事件,而每一個表格/索引將這些歷史變動映射爲當前的數據結構或索引。由於日誌的實時持久化的,所以它被視爲在系統崩潰時對其它持久化數據結構進行恢復的權威標準。
隨着時間流失,日誌從最開始ACID的事務實現方式,進化成了數據庫之間數據備飯的工具。事實證實,對數據庫執行的一系列變動記錄,正好能夠用於同步遠程的備份數據庫。Oracle,MySQL和PostgreSQL使用日誌傳輸協議將部分日誌發送到備份數據庫。
息傳輸,數據流和實時數據處理場景是很是合適的。
日誌解決的兩個問題:順序變化和分佈式數據,在分佈式數據系統中更爲重要。對更新操做的順序達成一致是這些系統進行設計時的核心問題。
這種在分佈式系統中以日誌爲中心的思想來源於一個簡單的觀察,我將其稱爲狀態機備份理論( State Machine Replication Principle):
若是兩個相同的,肯定的進程以同一個狀態開始,並以一樣的順序傳入相同的輸入,那麼它們會輸出相同的結果並以相同的狀態結束。
這看上去有點晦澀,讓咱們深刻理解其含義。
肯定性是指該過程和時間無關,而且不會讓任何外帶輸入影響其結果。好比一個程序的輸出,受特定的線程執行順序,或是調用獲取當前時間API或是其它非可重複的操做的影響,則將其視爲非肯定性的。
進程的狀態是指在程序執行完畢後,任何在內存中或是磁盤上存留下來的數據。
須要注意按照相同的順序傳入相同的輸入--這是日誌參與的部分。這是一個很是啓發性的理念:若是你向兩段肯定性的代碼片斷傳入相同的輸入日誌,它們會產生相同的輸出。
分佈式計算的應用顯而易見。你能夠將原先使用多臺機器作同一件事情,轉化爲實現一個分佈式一致性日誌來給這些進程相同的輸入流。這裏使用日誌的目的是將輸入流中的全部不肯定性排除掉,來確保每一個備份可使用這個輸入流進行同步。
一旦你理解了這一點,這個理念就再也不復雜了:它等因而說肯定性的處理得出的結論也是肯定的。儘管如此,我認爲它是一個更加通用的分佈式系統設計的工具。
這個方法的優勢之一是用來索引日誌的時間戳一樣能夠用來描述備份的狀態--你能夠用該備份處理過的最大日誌編號來描述該備份的狀態。這個時間戳結合日誌能夠惟一的描述整個備份當前所處的狀態。
在系統中使用這個思想的方式有不少,具體採用哪一種方式取決於將什麼內容寫入了日誌。好比,咱們能夠將調用服務的請求,或是服務在響應請求後狀態的變化,或是它執行的轉換指令寫入日誌。理論上來講,咱們甚至能夠將每一個備份執行的機器指令以及或是方法與參數名寫入日誌中。只要兩個進程用一樣的方式處理同一組輸入,這些進程就能夠在不一樣的備份中保持一致的狀態。
不一樣的人羣會用不一樣的方式描述日誌。數據庫開發者會區分物理日誌和邏輯日誌。物理日誌是指記錄每一行內容變動的日誌。而邏輯日誌是指不只記錄內容的變動,還會記錄致使內容變動的SQL日誌(插入,更新和刪除語句)。
分佈式系統語義下,一般區分兩種處理和備份的通用方法。狀態機模型一般指咱們將一組輸入請求寫入日誌,而每一個備份處理這些請求。對於這個模型進行略微修改的一個模型稱爲基本備份模型,它將一個備份選爲主機器,而且容許這個主機器按照請求到達的順序逐個處理請求,並將處理後的狀態變動寫入日誌中。其它的備份同步主機器的狀態變動,這樣當主機器崩潰後,其它機器能夠從新選舉出新的主機器並替代它。
要了解兩者之間的區別,先看一個簡單問題。假設如今有一個多備份的「計算服務」,它保存了一個數字做爲其狀態(該數字初始化爲0),而且對該值執行加減乘除操做。狀態機模型會將全部的變化操做寫入日誌,好比「+1」,「*2」等。而基本備份模型則會讓一個主節點執行計算操做,並記錄計算後的結果,如「1」,「3」,「6「。這個例子也證實了爲何順序性是在備份之間保持一致性的關鍵:加法和乘法操做的亂序會產生不一樣的結果。
其實我以爲,因爲歷史緣由,咱們關於日誌產生了一些偏見,也許是由於最近幾年分佈式計算的理論超越了其實際應用。在現實世界中,一致性問題被看的簡單了。計算機系統不多會對單個值執行一致性計算,它們一般須要處理一系列請求。所以日誌,相比於簡單的單值寄存器,是更常見的抽象。
不只如此,這些分佈式一致性算法,如Paxos,Raft等,並不太關注底層的日誌抽象系統。所以,咱們會將日誌視爲一個商業化的構建模塊,而不會關心其具體的實現細節。就像是當咱們討論哈希表時,不會關心咱們是經過線性探測法或是其它的方式來進行衝突解決的這種細節。。日誌就像是一個通用的商業化接口,它底下隱藏了不少的算法和實現,來確保提供最優性能。
再回到數據庫。關於變動日誌和表格有一個很特別的互補性。日誌就像是銀行處理的全部的借款和還款的記錄,而表格則記錄了當前帳戶的餘額。若是你記錄了變動日誌,就能夠經過執行這些變動來創造出捕獲當前狀態的表格。這個表格會記錄每一個健的最終狀態(或者說日誌的一個時刻)。所以日誌是更基礎的數據結構,除了來創造最終表格,還能夠經過它創造各類衍生的表格。
這個過程也能夠反過來執行:若是你有一個須要執行更新操做的表格,你可記錄這些變動併發佈一個包含全部變動的變動日誌給當前的表格。這個變動日誌就是須要提供給須要提供給近實時同步的備份的數據信息。所以,從這個角度來看,兩者是互補的:表格捕獲數據的當前狀態,而日誌捕獲變動。日誌的魅力在於,若是它捕獲了完整的變動日誌,它不只可以生成最終版本的表格,還可以生成任意其它版本的表格。它是對錶格過去的每個狀態的有效備份。
這可能會讓你想起源代碼版本控制。版本控制和數據庫之間存在緊密的關聯。版本控制和分佈式數據庫系統解決的問題很相似:管理狀態上分佈的,併發的變化。版本控制系統一般將一組修補變動進行建模,本質上也是一條日誌。開發者與代碼的一個快照進行交互,而快照就相似於表格。和別的分佈式有狀態的系統同樣,版本控制中的備份經過日誌來實現:當你更新代碼倉庫時,實際上是將全部代碼變動拉取下來,並將它們依次執行到當前的快照上。
在本文接下來的內容中,我會試着以超出分佈式計算或抽象的分佈式計算模型以外的領域來講明日誌的好處。包括:
這些應用都圍繞着將日誌做爲獨立服務的思想來實現。
在每一個場景下,日誌的易用性均來自於其提供的簡單功能:提供一個持久的,可重現的歷史記錄。使人驚訝的是,上述這些問題的核心都在於支持每臺機器須要可以按照本身的處理速度來處理肯定的歷史記錄。
數據集成是指支持全部的服務和系統均可以訪問該組織機構的全部數據。
數據集成並不是通用概念,可是我找不到更加合適的術語。而另外一個相對而言更加具備識別度的概念ETL只包含數據集成中的一部分操做--生成一個關聯型的數據倉庫。對於數據集成,你並無聽到太多關於大數據概念的使人窒息的炒做,但儘管如此,我相信「使數據可用」這個平凡的問題是任何一個組織機構能夠關注的更有價值的事情之一。
數據的有效使用某種程度上遵循了馬斯洛的需求層級模型。在金字塔的底端涉及了捕獲全部相關的數據,並可以將其放在一個可用的訪問環境中(好比一個實時訪問系統或是文本文件或是python腳本)。這個數據須要用通用的方式建模,使其易於讀取和處理。當這些通用的數據捕獲能力建設好以後,纔可以經過工具用各類方式處理這些數據--如MapReduce,實時查詢系統等。
須要注意一點:沒有一個可靠且完整的數據流的話,Hadoop集羣無異於一個昂貴且難以使用的工具。只有當數據和處理可用後,開發者才能將關注點移動到如何建好數據模型和易於理解的語境。最後,關注點能夠移動到更復雜的處理,好比可視化,報表和使用算法進行處理與預測。
從個人過往經驗看來,不少公司在基礎的數據收集上存在很大的缺陷。它們缺少可靠的完整的數據流,卻想要直接實施高端的數據建模技巧。這徹底是一種倒退。
因此問題在於,咱們如何可以橫跨公司的全部數據系統,構建一個可靠的數據流?
數據的第一個趨勢是時間數據的增長。事件數據記錄了發生了什麼事情,而非事情本分。在web系統中,這能夠是用戶的行爲日誌,也可以使機器級別的統計數據,用來監控數據中心的可靠性。人們傾向於將這些稱爲日誌數據,由於它們一般被寫入應用日誌中,但它會混淆形式和功能。其實這些數據在現代互聯網處於核心地位。畢竟谷歌的財富,就是經過點擊和事件之間的關聯創造的。
而這並不僅侷限於互聯網公司,只是由於互聯網公司已經電子化,所以這些數據更好管理。金融數據一直以來也是事件爲中心的。RFID將這種跟蹤添加到物理對象中。我認爲,隨着傳統商業的數字化,這一趨勢將繼續下去。
這種類型的事件數據記錄發生了什麼,而且一般比傳統的數據庫的數據量大幾個量級。這意味着處理上帶來了巨大的挑戰。
第二個趨勢是面向特殊場景的數據系統逐漸流行起來而且近五年來被普遍使用。這些數據系統包括OLAP,Elastic Search,批處理系統Hadoop,圖形系統graphlab等等。
各類類型數據的組合以及將這些數據流入各類類型的系統給數據集成帶來了巨大的問題。
日誌是處理系統間數據流的通用數據結構。它的思路很簡單:將公司的全部數據收集起來放入日誌中心,並提供實時的數據訂閱。
每個邏輯數據源能夠視爲獨立的日誌。數據源能夠是應用將事件(如點擊或頁面瀏覽)打印的日誌,或是數據庫表格的變動操做日誌。每個訂閱系統儘量快速的閱讀這些日誌,將這些日誌應用於本身的數據上,並繼續推動日誌的閱讀。訂閱者能夠是任何數據系統--緩存,Hadoop,其餘地址的數據庫,搜索引擎等。
好比,日誌的概念使得每個訂閱者執行的變動進度能夠經過邏輯時鐘度量。它經過每一個訂閱者當前訂閱的時間節點進度,簡化了不一樣訂閱者彼此之間的狀態比較。
想象一個簡單的場景,如今有一個數據庫和一組緩存服務器。日誌提供了一種同步更新到全部這些系統的方式,並可以告知當前系統的同步進度。假設咱們寫了一條日誌X而且須要從緩存執行一個讀操做。假設咱們想要確保不會讀到過時數據,只須要確保不要從任何還未同步日誌X的緩存服務器讀取便可。
日誌還能夠充當實現數據異步消費的緩存。這一點很重要,尤爲是在存在多個消費速率各不相同的消費者的場景下。這意味着訂閱系統崩潰或是停服維護後,重啓時能夠馬上恢復狀態。批處理系統如Haddop或是數據倉庫可能會以小時級或是天級來進行消費,而實時查詢系統可能須要以秒級消費。數據源和日誌都無需感知各類類型的目標數據系統,所以各個消費系統能夠透明的從流水線上插入或移除。
最重要的是,每個目標系統都只須要了解日誌而無需感知日誌源系統的任何細節。消費系統不須要知道誰究竟來自關係型數據庫,一個新型的鍵值數據庫,仍是實時數據流。這一點看上去微不足道,實則相當重要。
這裏我使用日誌的概念而非消息系統或是消費訂閱,是由於它相對而言在語義上更加具體,而且更詳細地描述了在實際實現中支持數據複製所需的內容。我發現發佈訂閱這個詞只能表達出非直接的消息路由。若是比較任意兩個發佈訂閱的消息系統,會發現它們有徹底不一樣的實現機制,並且大多數的模型在這個領域中並不適用。你能夠將日誌視爲一種消息系統,它實現了持久性和強順序性。在分佈式系統中,這種通訊模型有時被稱爲原子廣播。
使用日誌做爲數據流的結構已經在領英存在好久了。早期咱們開發的一個基礎設施就是databus,它可以在Oracle數據表之上創建日誌緩存抽象,從而將這些數據庫變動同步給社交圖庫和搜索引擎。
對此我再提供一些上下文。我最初參與這個項目中是在2008年去構建鍵值數據庫,個人下一個項目是試着去搭建一個Hadoop集羣,並將一些推薦進程遷移至其上。對此咱們都沒什麼經驗,所以咱們先用了幾周時間來實現數據的寫入和輸出,再用剩餘的時間實現各類高貴的預測算法。
最初咱們的設想是將數據從現有的Oracle數據倉庫中剝離出來。接着就發現快速將數據從Oracle中提取出來是一門黑科技。更糟糕的是,Oracle的數據倉庫的處理過程並不適合咱們爲Hadoop預設的批處理過程--它們大可能是不可逆且特定於某種報表。因而咱們再也不使用數據倉庫,而是直接從源數據庫和日誌文件來獲取數據。最終,咱們實現了其它的數據流水線將數據導入咱們的鍵值數據庫。
這種平平無奇的數據複製最終成爲項目開發的主要工做。更糟糕的是,任何一個流水線中出現問題,Hadoop系統很大程度上就無用了,在髒數據上運行高端的算法只會產生更髒的數據。
雖然咱們已經儘量用通用的方式來構建,可是每當一個新的數據源出現時,都須要配置自定義的配置。它還成了大量錯誤和異常的來源。咱們基於Hadoop實現的特性逐漸流行起來,並吸引了不少人的興趣。每一個業務方都有一些想要與這個系統集成的系統,以及一組關心的數據流。
慢慢的一些事情漸漸清晰起來。
首先,咱們以前構建的流水線,雖然有一點凌亂,倒是很是重要的。僅僅是將數據在另外一個數據處理系統中可見就解鎖了無限的可能。不少新的產品和分析都是基於將原來分散在多個特殊系統中的數據片斷歸攏在一塊兒。
其次,可靠的數據流須要數據管道的更多支持。若是咱們可以捕獲所需的全部數據結構,就能讓Hadoop數據流完全自動化,從而在新增數據源或是處理結構變化時無需手動執行這些操做,它們會自動爲新的數據源建立合適的列。
第三點,咱們的數據覆蓋率很低。也就是說,若是你觀察在Hadoop上保存的LinkedIn數據比例,它仍是至關不完整的。而要想完善數據並不容易,考慮到爲每個新的數據源執行定製化所須要的成本。
咱們爲每一個數據源和數據目標構建自定義數據加載的方式顯然是不可行的。咱們有幾十個數據系統和數據倉庫。鏈接全部這些將致使在每對系統之間創建自定義管道,以下所示:
注意,數據一般是雙向流動的,由於許多系統(數據庫、Hadoop)都是數據傳傳輸源和目的地。這意味着咱們最終將爲每一個系統構建兩個管道:一個用於獲取數據,另外一個用於獲取數據。
很明顯,這須要一大堆人來建設,並且永遠沒法運做。當咱們接近徹底連通時,咱們最終會獲得相似於O(N2)的管道。
相反,咱們須要這樣的通用工具:
As much as possible, we needed to isolate each consumer from the source of the data. They should ideally integrate with just a single data repository that would give them access to everything.
咱們須要儘量地將每一個消費者與數據源隔離開來。理想狀況下,它們應該只與一個單一的數據存儲庫集成,支持它們訪問全部內容。
其思想是,添加一個新的數據系統(不管是數據源仍是數據目的地)只負責集成工做,將每一個數據源鏈接到單個管道,而不是每一個數據使用者。
這一經驗使我專一於構建Kafka,將咱們在消息傳遞系統中看到的內容與數據庫和分佈式系統內部流行的日誌概念結合起來。咱們但願它首先做爲全部活動數據的中心管道,並最終用於許多其餘用途,包括從Hadoop部署數據、監視數據等。
很長一段時間以來,Kafka做爲一種基礎設施產品(既不是數據庫,也不是日誌文件收集系統,也不是傳統的消息傳遞系統)有點獨特(有些人會說很奇怪)。但最近亞馬遜提供了一種與Kafka很是類似的服務,叫作kinisis。這種類似點能夠體如今分區的處理方式、數據的保留,以及Kafka API中高層和底層消費者之間至關奇怪的分離。我對此很高興。建立了一個良好的基礎架構抽象的標誌是,AWS將其做爲服務提供!他們對此的設想彷佛與我所描述的徹底類似:它是鏈接全部分佈式系統DynamoDB、RedShift、S3等的管道,也是使用EC2進行分佈式流處理的基礎。