天天看點

《銀行的一天》示範日志處理保序、不丢、不重

日志處理是一個很大範疇,其中包括實時計算、資料倉庫、離線計算等衆多點。這篇文章主要讨論如何在實時計算場景中,如何能做到日志處理保序、不丢失、不重複,并且在上下遊業務系統不可靠(存在故障),業務流量劇烈波動情況下,如何保持這三點。

為了能夠友善了解,這裡會使用《銀行的一天》作為例子将概念解釋清楚。在篇幅的末尾,我會介紹下日志服務loghub功能,是如何與spark streaming、storm spout等配合,完成日志資料的處理過程。

《銀行的一天》示範日志處理保序、不丢、不重

append only:日志是一種追加模式,一旦産生過後就無法修改

totally order by time:嚴格有序,每條日志有一個确定時間點。不同日志在秒級時間次元上可能有重複,比如有2個操作get、set發生在同一秒鐘,但對于計算機而言這兩個操作也是有順序的

什麼樣的資料可以抽象成日志?

半世紀前說起日志,想到的是船長、操作員手裡厚厚的筆記。如今計算機誕生使得日志産生與消費無處不在:伺服器、路由器、傳感器、gps、訂單、及各種裝置通過不同角度描述着我們生活的世界。從船長日志中我們可以發現,日志除了帶一個記錄的時間戳外,可以包含幾乎任意的内容,例如:一段記錄文字、一張圖檔、天氣狀況、船行方向等。幾個世紀過去了,“船長日志”的方式已經擴充到一筆訂單、一項付款記錄、一次使用者通路、一次資料庫操作等多樣的領域。

在計算機世界中,常用的日志有:metric,binlog(database、nosql),event,auditing,access log 等。

在我們今天的示範例子中,我們把使用者到銀行的一次操作作為一條日志資料。其中包括使用者、賬号名、操作時間、操作類型、操作金額等。

例如:

log: 由時間、及一組key,value對組成

loggroup: 一組日志的集合,包含相同meta(ip,source)等

兩者關系如下:

《銀行的一天》示範日志處理保序、不丢、不重

shard: 分區,loggroup讀寫基本單元,可以了解為48小時為周期的fifo隊列。每個shard提供 5 mb/s write, 10 mb/s read能力。shard 有邏輯區間(beginkey,endkey)用以歸納不同類型資料

logstore:日志庫,用以存放同一類日志資料。logstore是一個載體,通過由[0000, ffff..)區間shard組合建構而成,logstore會包含1個或多個shard

project: logstore存放容器

這些概念互相關系如下:

《銀行的一天》示範日志處理保序、不丢、不重

我們來以19世紀銀行來舉例子,城市裡有若幹使用者(producer),到銀行去存取錢(user operation),銀行有若幹個櫃員(consumer)。因為19世紀還沒有電腦可以實時同步,是以每個櫃員都有一個小賬本能夠記錄對應資訊,每天晚上把錢和賬本拿到公司去對賬。

在分布式世界裡,我們可以把櫃員認為是固定記憶體和計算能力單機。使用者是來自各個資料源的請求,bank大廳是處理使用者存取資料的日志庫(logstore)。

《銀行的一天》示範日志處理保序、不丢、不重

log/loggroup:使用者發出的存取款等操作

使用者(user):log/loggroup生産者

櫃員(clerk):銀行處理使用者請求的員工

銀行大廳(logstore):使用者産生的操作請求先進入銀行大廳,再交給櫃員處理

分區(shard):銀行大廳用以安排使用者請求的組織方式

銀行有2個櫃員(a,b),張三進了銀行,在櫃台a上存了1000元,a把張三1000元存在自己的賬本上。張三到了下午覺得手頭緊到b櫃台取錢,b櫃員一看賬本。不對啊,你沒有在我這裡存錢?

從這個例子可以看到,存取款是一個嚴格有序的操作,需要同一個櫃員(處理器)來處理同一個使用者的操作,這樣才能保持狀态一緻性。

《銀行的一天》示範日志處理保序、不丢、不重

實作保序的方法很簡單:排隊,建立一個shard,終端隻有一個櫃員a來處理。使用者請求先進先出,一點問題都沒有。但帶來的問題是效率低下,假設有1000個使用者來進行操作,即使有10個櫃員也無濟于事。

這種場景怎麼辦?

假設有10個櫃員,我們可以建立10個shard

如何保證對于同一個賬戶的操作是有序的?可以根據一緻性hash方式将使用者進行映射。例如我們開10個隊伍(shard),每個櫃員處理一個shard,把不同銀行賬号或使用者姓名,映射到特定shard中。在這種情況下張三 hash(zhang)= z 永遠落在一個特定shard中(區間包含z),處理端面對的永遠是櫃員a。

當然如果張姓使用者比較多,也可以換其他政策。例如根據使用者accountid、zipcode進行hash,這樣就可以使得每個shard中操作請求更均勻。

《銀行的一天》示範日志處理保序、不丢、不重

張三拿着存款在櫃台a處理,櫃員a處理到一半去接了個電話,等回來後以為業務已經辦理好了,于是開始處理下一個使用者的請求,張三的存款請求是以被丢失。

雖然機器不會人為犯錯,線上時間和可靠性要比櫃員高。但難免也會遇到當機、或因負載高導緻的進行中斷,因為這樣的場景丢失使用者的存款,這是萬萬不行的。

這種情況怎麼辦呢?

a可以在自己日記本上(非賬本)記錄一個項目:目前已處理到shard哪個位置,隻有當張三的這個存款請求被完全确認後,櫃員a才能叫下一個。

《銀行的一天》示範日志處理保序、不丢、不重

帶來問題是什麼?可能會重複。比如a已經處理完張三請求(更新賬本),準備在日記本上記錄處理到哪個位置之時,突然被叫開了,當他回來後,發現張三請求沒有記錄下來,他會把張三請求再次處理一遍,這就會造成重複。

重複一定會帶來問題嗎?不一定。

在幂等情況下,重複雖然會有浪費,但對結果沒有影響。什麼叫幂等:重複消費不對結果産生影響的操作叫做幂等。例如使用者有一個操作 “查詢餘額”,該操作是一個隻讀操作,重複做不影響結果。對于非隻讀操作,例如登出使用者這類操作,可以連續做兩次。

但現實生活中大部分操作不是幂等的,例如存款、取款等,重複進行計算會對結果帶來緻命的影響。解決的方式是什麼呢?櫃員(a)需要把賬本完成 + 日記本标記shard中處理完成作為一個事物合并操作,并記錄下來(checkpoint)。

如果a暫時離開或永久離開,其他櫃員隻要使用相同的規範:記錄中已操作則處理下一個即可,如果沒有則重複做,過程中需要保證原子性。

《銀行的一天》示範日志處理保序、不丢、不重

checkpoint可以将shard 中的元素位置(或時間)作為key,放入一個可以持久化的對象中。代表目前元素已經被處理完成。

以上三個概念解釋完成後,原理并不複雜。但在現實世界中,規模的變化與不确定性會使得以上三個問題便得更複雜。例如:

遇到發工資日子,使用者數會大漲

櫃員(clerk)畢竟不是機器人,他們需要休假,需要吃午飯

銀行經理為了整體服務體驗,需要加快櫃員,以什麼作為判斷标準?shard中處理速度?

櫃員在交接過程中,能否非常容易地傳遞賬本與記錄?

隻有一個shard0,使用者請求全部排在shard0下,櫃員a也正好可以處理

《銀行的一天》示範日志處理保序、不丢、不重

銀行經理決定把10點後shard0分裂成2個新shard(shard1,shard2),并且給了如下規定,姓名是[a-w]使用者到shard1中排隊,姓名是[x, y, z] 到shard 2 中排隊等待處理,為什麼這兩個shard區間不均勻?因為使用者的姓氏本身就是不均勻的,通過這種映射方式可以保證櫃員處理的均衡。

10-12點請求消費狀态:

《銀行的一天》示範日志處理保序、不丢、不重

櫃員a處理2個shard非常吃力,于是經理派出櫃員b、c出廠。因為隻有2個shard,b開始接管a負責一個shard,c處于閑置狀态。

銀行經理覺得shard1下櫃員a壓力太大,是以從shard1中分裂出(shard3,shard4)兩個新的shard,shard3由櫃員a處理、shard4由櫃員c處理。在12點後原來排在shard 1中的請求,分别到shard3,shard4中。

12點後請求消費狀态:

《銀行的一天》示範日志處理保序、不丢、不重

是以銀行經理讓櫃員a、b休息,讓c同僚處理shard2,shard3,shard4中的請求。并逐漸将shard2與shard3合并成shard5,最後将shard5和shard4合并成一個shard,當處理完成shard中所有請求後銀行關門。

上述過程可以抽象成日志處理的經典場景,如果要解決銀行的業務需求,我們要提供彈性伸縮、并且靈活适配的日志基礎架構,包括:

過程中不重複(需要消費者配合)

通過loghub + loghub consumer library 能夠幫助你解決日志實時進行中的這些經典問題,隻需把精力放在業務邏輯上,而不用去擔心流量擴容、failover等家常瑣事,是不是很爽?

繼續閱讀