大多數(shù)開發(fā)者已經(jīng)習(xí)慣了無狀態(tài)服務(wù)的理念,傾向于將所有數(shù)據(jù)存放在遠(yuǎn)端數(shù)據(jù)庫中,難以理解流式計(jì)算中為何需要「局部狀態(tài)」的存在。此文將闡述流計(jì)算中「局部狀態(tài)」的含義、動機(jī)、適用場景和優(yōu)劣勢。
什么是狀態(tài)?
想象你在使用 SQL 執(zhí)行一些操作。
如果所有請求都只需要操作單行數(shù)據(jù)(如使用主鍵ID執(zhí)行基本的 select
檢索操作),那么此服務(wù)對數(shù)據(jù)的依賴可以稱之為是「無狀態(tài)」的。
然而現(xiàn)實(shí)場景中往往存在各類聚合(aggregation)、聯(lián)合(join)操作。
聚合操作如:在某段時間窗口內(nèi),對若干頁面的廣告點(diǎn)擊率(CTR,click-through rate)進(jìn)行統(tǒng)計(jì)。
流連接(streaming join)操作如:廣告展示(ad impression)數(shù)據(jù)流同廣告點(diǎn)擊率數(shù)據(jù)流兩者存在時間先后順序,但又是強(qiáng)關(guān)聯(lián)的,需進(jìn)行流連接操作。
字段填充(enrichment)操作如:給僅包含用戶ID的廣告點(diǎn)擊率數(shù)據(jù)流,補(bǔ)充更詳細(xì)的用戶屬性值,以便下游分析系統(tǒng)處理。
遠(yuǎn)程狀態(tài)
一種常見的處理模式是,從輸入流中依次獲取記錄,對每條記錄執(zhí)行若干次針對遠(yuǎn)程分布式數(shù)據(jù)庫的請求。
如上圖可見,數(shù)據(jù)流被分派至多個機(jī)器上的多個處理單元(processor)上進(jìn)行處理,每個處理單元都會發(fā)起對遠(yuǎn)端的分布式數(shù)據(jù)庫的請求;由于分布式數(shù)據(jù)庫的特性,這些請求最終落在位于不同機(jī)器的各個數(shù)據(jù)庫分區(qū)(database partition)上。另一種可能的做法是,將處理單元和數(shù)據(jù)庫分區(qū)「綁起來」(co-located),讓請求不用兜一個大圈子、而是直接在本機(jī)上進(jìn)行處理,以提速數(shù)據(jù)流的處理。這種做法中,同處理單元綁定的數(shù)據(jù)存儲分區(qū),我們稱之為「局部狀態(tài)」(local state)。
局部狀態(tài)
滿足如下條件的,都可以稱之為局部狀態(tài)。
同處理單元位于同一臺機(jī)器上。
處理單元可根據(jù)輸入數(shù)據(jù)流,查詢/修改其中的狀態(tài)數(shù)據(jù)。
存儲于內(nèi)存或者磁盤中。
在闡述局部狀態(tài)的好處前,我們先嘗試回答一個顯而易見的問題:局部狀態(tài)如何做到高可用,如果機(jī)器掛了怎么辦?
容錯機(jī)制
Samza 對于局部狀態(tài)提供的容錯機(jī)制是,將局部狀態(tài)的變更(local state changes)建模成一種提交日志/變更日志(commit/change log),從而利用提交日志的可重放(replay)特性來保障容錯。
處理單元執(zhí)行變更狀態(tài)時,將變更日志寫入 Kafka topic 中。如果機(jī)器掛掉,那么新啟動的處理單元從上述 Kafka topic 中回放變更日志,從而重建機(jī)器掛掉前的局部狀態(tài)。利用 Kafka 周期性的日志壓縮(log compaction)操作,能夠?qū)⑷罩玖靠刂圃诤侠淼拇笮?、而不會隨著時間日益