Kafka在0.11.0.0以前的版本中只支持At Least Once
和At Most Once
語義,尚不支持Exactly Once
語義。服務器
可是在不少要求嚴格的場景下,如使用Kafka處理交易數據,Exactly Once
語義是必須的。咱們能夠經過讓下游系統具備冪等性來配合Kafka的At Least Once
語義來間接實現Exactly Once
。可是:設計
所以,Kafka自己對Exactly Once
語義的支持就很是必要。code
操做的原子性是指,多個操做要麼所有成功要麼所有失敗,不存在部分紅功部分失敗的可能。事務
實現原子性操做的意義在於:get
上文提到,實現Exactly Once
的一種方法是讓下游系統具備冪等處理特性,而在Kafka Stream中,Kafka Producer自己就是「下游」系統,所以若是能讓Producer具備冪等處理特性,那就可讓Kafka Stream在必定程度上支持Exactly once
語義。kafka
爲了實現Producer的冪等語義,Kafka引入了Producer ID
(即PID
)和Sequence Number
。每一個新的Producer在初始化的時候會被分配一個惟一的PID,該PID對用戶徹底透明而不會暴露給用戶。it
對於每一個PID,該Producer發送數據的每一個<Topic, Partition>
都對應一個從0開始單調遞增的Sequence Number
。io
相似地,Broker端也會爲每一個<PID, Topic, Partition>
維護一個序號,而且每次Commit一條消息時將其對應序號遞增。對於接收的每條消息,若是其序號比Broker維護的序號(即最後一次Commit的消息的序號)大一,則Broker會接受它,不然將其丟棄:ast
InvalidSequenceNumber
DuplicateSequenceNumber
上述設計解決了0.11.0.0以前版本中的兩個問題:原理
上述冪等設計只能保證單個Producer對於同一個<Topic, Partition>
的Exactly Once
語義。
另外,它並不能保證寫操做的原子性——即多個寫操做,要麼所有被Commit要麼所有不被Commit。
更不能保證多個讀寫操做的的原子性。尤爲對於Kafka Stream應用而言,典型的操做便是從某個Topic消費數據,通過一系列轉換後寫回另外一個Topic,保證從源Topic的讀取與向目標Topic的寫入的原子性有助於從故障中恢復。
事務保證可以使得應用程序將生產數據和消費數據看成一個原子單元來處理,要麼所有成功,要麼所有失敗,即便該生產或消費跨多個<Topic, Partition>
。
另外,有狀態的應用也能夠保證重啓後從斷點處繼續處理,也即事務恢復。
爲了實現這種效果,應用程序必須提供一個穩定的(重啓後不變)惟一的ID,也即Transaction ID
。Transactin ID
與PID
可能一一對應。區別在於Transaction ID
由用戶提供,而PID
是內部的實現對用戶透明。
另外,爲了保證新的Producer啓動後,舊的具備相同Transaction ID
的Producer即失效,每次Producer經過Transaction ID
拿到PID的同時,還會獲取一個單調遞增的epoch。因爲舊的Producer的epoch比新Producer的epoch小,Kafka能夠很容易識別出該Producer是老的Producer並拒絕其請求。
有了Transaction ID
後,Kafka可保證:
Transaction ID
的新的Producer實例被建立且工做時,舊的且擁有相同Transaction ID
的Producer將再也不工做。這一節所說的事務主要指原子性,也即Producer將多條消息做爲一個事務批量發送,要麼所有成功要麼所有失敗。
爲了實現這一點,Kafka 0.11.0.0引入了一個服務器端的模塊,名爲Transaction Coordinator
,用於管理Producer發送的消息的事務性。
該Transaction Coordinator
維護Transaction Log
,該log存於一個內部的Topic內。因爲Topic數據具備持久性,所以事務的狀態也具備持久性。
Producer並不直接讀寫Transaction Log
,它與Transaction Coordinator
通訊,而後由Transaction Coordinator
將該事務的狀態插入相應的Transaction Log
。
Transaction Log
的設計與Offset Log
用於保存Consumer的Offset相似。
許多基於Kafka的應用,尤爲是Kafka Stream應用中同時包含Consumer和Producer,前者負責從Kafka中獲取消息,後者負責將處理完的數據寫回Kafka的其它Topic中。
爲了實現該場景下的事務的原子性,Kafka須要保證對Consumer Offset的Commit與Producer對發送消息的Commit包含在同一個事務中。不然,若是在兩者Commit中間發生異常,根據兩者Commit的順序可能會形成數據丟失和數據重複:
At Least Once
語義,可能形成數據重複。At Most Once
語義,可能形成數據丟失。PID
與Sequence Number
的引入實現了寫操做的冪等性At Least Once
語義實現了單一Session內的Exactly Once
語義Transaction Marker
與PID
提供了識別消息是否應該被讀取的能力,從而實現了事務的隔離性Transaction Marker
)來實現事務中涉及的全部讀寫操做同時對外可見或同時對外不可見