原創文章,首發自作者個人博客,轉載請務必將下面這段話置於文章開頭處。
本文轉發自技術世界,原文鏈接 http://www.jasongj.com/kafka/transaction/
1 寫在前面的話
本文所有Kafka原理性的描述除特殊說明外均基於Kafka 1.0.0版本。
2 為什么要提供事務機制
Kafka事務機制的實現主要是為了支持
Exactly Once
即正好一次語義- 操作的原子性
- 有狀態操作的可恢復性
2.1 Exactly Once
《Kafka背景及架構介紹》一文中有說明Kafka在0.11.0.0之前的版本中只支持At Least Once
和At Most Once
語義,尚不支持Exactly Once
語義。
但是在很多要求嚴格的場景下,如使用Kafka處理交易數據,Exactly Once
語義是必須的。我們可以通過讓下游系統具有冪等性來配合Kafka的At Least Once
語義來間接實現Exactly Once
。但是:
- 該方案要求下游系統支持冪等操作,限制了Kafka的適用場景
- 實現門檻相對較高,需要用戶對Kafka的工作機制非常了解
- 對於Kafka Stream而言,Kafka本身即是自己的下游系統,但Kafka在0.11.0.0版本之前不具有冪等發送能力
因此,Kafka本身對Exactly Once
語義的支持就非常必要。
2.2 操作原子性
操作的原子性是指,多個操作要么全部成功要么全部失敗,不存在部分成功部分失敗的可能。
實現原子性操作的意義在於:
- 操作結果更可控,有助於提升數據一致性
- 便於故障恢復。因為操作是原子的,從故障中恢復時只需要重試該操作(如果原操作失敗)或者直接跳過該操作(如果原操作成功),而不需要記錄中間狀態,更不需要針對中間狀態作特殊處理
3 實現事務機制的幾個階段
3.1 冪等性發送
上文提到,實現Exactly Once
的一種方法是讓下游系統具有冪等處理特性,而在Kafka Stream中,Kafka Producer本身就是“下游”系統,因此如果能讓Producer具有冪等處理特性,那就可以讓Kafka Stream在一定程度上支持Exactly once
語義。
為了實現Producer的冪等語義,Kafka引入了Producer ID
(即PID
)和Sequence Number
。每個新的Producer在初始化的時候會被分配一個唯一的PID,該PID對用戶完全透明而不會暴露給用戶。
對於每個PID,該Producer發送數據的每個<Topic, Partition>
都對應一個從0開始單調遞增的Sequence Number
。
類似地,Broker端也會為每個<PID, Topic, Partition>
維護一個序號,並且每次Commit一條消息時將其對應序號遞增。對於接收的每條消息,如果其序號比Broker維護的序號(即最后一次Commit的消息的序號)大一,則Broker會接受它,否則將其丟棄:
- 如果消息序號比Broker維護的序號大一以上,說明中間有數據尚未寫入,也即亂序,此時Broker拒絕該消息,Producer拋出
InvalidSequenceNumber
- 如果消息序號小於等於Broker維護的序號,說明該消息已被保存,即為重復消息,Broker直接丟棄該消息,Producer拋出
DuplicateSequenceNumber
上述設計解決了0.11.0.0之前版本中的兩個問題:
- Broker保存消息后,發送ACK前宕機,Producer認為消息未發送成功並重試,造成數據重復
- 前一條消息發送失敗,后一條消息發送成功,前一條消息重試后成功,造成數據亂序
3.2 事務性保證
上述冪等設計只能保證單個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可保證:
- 跨Session的數據冪等發送。當具有相同
Transaction ID
的新的Producer實例被創建且工作時,舊的且擁有相同Transaction ID
的Producer將不再工作。 - 跨Session的事務恢復。如果某個應用實例宕機,新的實例可以保證任何未完成的舊的事務要么Commit要么Abort,使得新實例從一個正常狀態開始工作。
需要注意的是,上述的事務保證是從Producer的角度去考慮的。從Consumer的角度來看,該保證會相對弱一些。尤其是不能保證所有被某事務Commit過的所有消息都被一起消費,因為:
- 對於壓縮的Topic而言,同一事務的某些消息可能被其它版本覆蓋
- 事務包含的消息可能分布在多個Segment中(即使在同一個Partition內),當老的Segment被刪除時,該事務的部分數據可能會丟失
- Consumer在一個事務內可能通過seek方法訪問任意Offset的消息,從而可能丟失部分消息
- Consumer可能並不需要消費某一事務內的所有Partition,因此它將永遠不會讀取組成該事務的所有消息
4 事務機制原理
4.1 事務性消息傳遞
這一節所說的事務主要指原子性,也即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類似。
4.2 事務中Offset的提交
許多基於Kafka的應用,尤其是Kafka Stream應用中同時包含Consumer和Producer,前者負責從Kafka中獲取消息,后者負責將處理完的數據寫回Kafka的其它Topic中。
為了實現該場景下的事務的原子性,Kafka需要保證對Consumer Offset的Commit與Producer對發送消息的Commit包含在同一個事務中。否則,如果在二者Commit中間發生異常,根據二者Commit的順序可能會造成數據丟失和數據重復:
- 如果先Commit Producer發送數據的事務再Commit Consumer的Offset,即
At Least Once
語義,可能造成數據重復。 - 如果先Commit Consumer的Offset,再Commit Producer數據發送事務,即
At Most Once
語義,可能造成數據丟失。
4.3 用於事務特性的控制型消息
為了區分寫入Partition的消息被Commit還是Abort,Kafka引入了一種特殊類型的消息,即Control Message
。該類消息的Value內不包含任何應用相關的數據,並且不會暴露給應用程序。它只用於Broker與Client間的內部通信。
對於Producer端事務,Kafka以Control Message的形式引入一系列的Transaction Marker
。Consumer即可通過該標記判定對應的消息被Commit了還是Abort了,然后結合該Consumer配置的隔離級別決定是否應該將該消息返回給應用程序。
4.4 事務處理樣例代碼
Producer<String, String> producer = new KafkaProducer<String, String>(props);
// 初始化事務,包括結束該Transaction ID對應的未完成的事務(如果有)
// 保證新的事務在一個正確的狀態下啟動
producer.initTransactions();
// 開始事務
producer.beginTransaction();
// 消費數據
ConsumerRecords<String, String> records = consumer.poll(100);
try{
// 發送數據
producer.send(new ProducerRecord<String, String>("Topic", "Key", "Value"));
// 發送消費數據的Offset,將上述數據消費與數據發送納入同一個Transaction內
producer.sendOffsetsToTransaction(offsets, "group1");
// 數據發送及Offset發送均成功的情況下,提交事務
producer.commitTransaction();
} catch (ProducerFencedException | OutOfOrderSequenceException | AuthorizationException e) {
// 數據發送或者Offset發送出現異常時,終止事務
producer.abortTransaction();
} finally {
// 關閉Producer和Consumer
producer.close();
consumer.close();
}
4.5 完整事務過程

4.5.1 找到Transaction Coordinator
由於Transaction Coordinator
是分配PID和管理事務的核心,因此Producer要做的第一件事情就是通過向任意一個Broker發送FindCoordinator
請求找到Transaction Coordinator
的位置。
注意:只有應用程序為Producer配置了Transaction ID
時才可使用事務特性,也才需要這一步。另外,由於事務性要求Producer開啟冪等特性,因此通過將transactional.id
設置為非空從而開啟事務特性的同時也需要通過將enable.idempotence
設置為true來開啟冪等特性。
4.5.2 獲取PID
找到Transaction Coordinator
后,具有冪等特性的Producer必須發起InitPidRequest
請求以獲取PID。
注意:只要開啟了冪等特性即必須執行該操作,而無須考慮該Producer是否開啟了事務特性。
*** 如果事務特性被開啟 ***
InitPidRequest
會發送給Transaction Coordinator
。如果Transaction Coordinator
是第一次收到包含有該Transaction ID
的InitPidRequest請求,它將會把該<TransactionID, PID>
存入Transaction Log
,如上圖中步驟2.1所示。這樣可保證該對應關系被持久化,從而保證即使Transaction Coordinator
宕機該對應關系也不會丟失。
除了返回PID外,InitPidRequest
還會執行如下任務:
- 增加該PID對應的epoch。具有相同PID但epoch小於該epoch的其它Producer(如果有)新開啟的事務將被拒絕。
- 恢復(Commit或Abort)之前的Producer未完成的事務(如果有)。
注意:InitPidRequest
的處理過程是同步阻塞的。一旦該調用正確返回,Producer即可開始新的事務。
另外,如果事務特性未開啟,InitPidRequest
可發送至任意Broker,並且會得到一個全新的唯一的PID。該Producer將只能使用冪等特性以及單一Session內的事務特性,而不能使用跨Session的事務特性。
4.5.3 開啟事務
Kafka從0.11.0.0版本開始,提供beginTransaction()
方法用於開啟一個事務。調用該方法后,Producer本地會記錄已經開啟了事務,但Transaction Coordinator
只有在Producer發送第一條消息后才認為事務已經開啟。
4.5.4 Consume-Transform-Produce
這一階段,包含了整個事務的數據處理過程,並且包含了多種請求。
AddPartitionsToTxnRequest
一個Producer可能會給多個<Topic, Partition>
發送數據,給一個新的<Topic, Partition>
發送數據前,它需要先向Transaction Coordinator
發送AddPartitionsToTxnRequest
。
Transaction Coordinator
會將該<Transaction, Topic, Partition>
存於Transaction Log
內,並將其狀態置為BEGIN
,如上圖中步驟4.1所示。有了該信息后,我們才可以在后續步驟中為每個Topic, Partition>
設置COMMIT或者ABORT標記(如上圖中步驟5.2所示)。
另外,如果該<Topic, Partition>
為該事務中第一個<Topic, Partition>
,Transaction Coordinator
還會啟動對該事務的計時(每個事務都有自己的超時時間)。
ProduceRequest
Producer通過一個或多個ProduceRequest
發送一系列消息。除了應用數據外,該請求還包含了PID,epoch,和Sequence Number
。該過程如上圖中步驟4.2所示。
AddOffsetsToTxnRequest
為了提供事務性,Producer新增了sendOffsetsToTransaction
方法,該方法將多組消息的發送和消費放入同一批處理內。
該方法先判斷在當前事務中該方法是否已經被調用並傳入了相同的Group ID。若是,直接跳到下一步;若不是,則向Transaction Coordinator
發送AddOffsetsToTxnRequests
請求,Transaction Coordinator
將對應的所有<Topic, Partition>
存於Transaction Log
中,並將其狀態記為BEGIN
,如上圖中步驟4.3所示。該方法會阻塞直到收到響應。
TxnOffsetCommitRequest
作為sendOffsetsToTransaction
方法的一部分,在處理完AddOffsetsToTxnRequest
后,Producer也會發送TxnOffsetCommit
請求給Consumer Coordinator
從而將本事務包含的與讀操作相關的各<Topic, Partition>
的Offset持久化到內部的__consumer_offsets
中,如上圖步驟4.4所示。
在此過程中,Consumer Coordinator
會通過PID和對應的epoch來驗證是否應該允許該Producer的該請求。
這里需要注意:
- 寫入
__consumer_offsets
的Offset信息在當前事務Commit前對外是不可見的。也即在當前事務被Commit前,可認為該Offset尚未Commit,也即對應的消息尚未被完成處理。 Consumer Coordinator
並不會立即更新緩存中相應<Topic, Partition>
的Offset,因為此時這些更新操作尚未被COMMIT或ABORT。
4.5.5 Commit或Abort事務
一旦上述數據寫入操作完成,應用程序必須調用KafkaProducer
的commitTransaction
方法或者abortTransaction
方法以結束當前事務。
EndTxnRequest
commitTransaction
方法使得Producer寫入的數據對下游Consumer可見。abortTransaction
方法通過Transaction Marker
將Producer寫入的數據標記為Aborted
狀態。下游的Consumer如果將isolation.level
設置為READ_COMMITTED
,則它讀到被Abort的消息后直接將其丟棄而不會返回給客戶程序,也即被Abort的消息對應用程序不可見。
無論是Commit還是Abort,Producer都會發送EndTxnRequest
請求給Transaction Coordinator
,並通過標志位標識是應該Commit還是Abort。
收到該請求后,Transaction Coordinator
會進行如下操作
- 將
PREPARE_COMMIT
或PREPARE_ABORT
消息寫入Transaction Log
,如上圖中步驟5.1所示 - 通過
WriteTxnMarker
請求以Transaction Marker
的形式將COMMIT
或ABORT
信息寫入用戶數據日志以及Offset Log
中,如上圖中步驟5.2所示 - 最后將
COMPLETE_COMMIT
或COMPLETE_ABORT
信息寫入Transaction Log
中,如上圖中步驟5.3所示
補充說明:對於commitTransaction
方法,它會在發送EndTxnRequest
之前先調用flush方法以確保所有發送出去的數據都得到相應的ACK。對於abortTransaction
方法,在發送EndTxnRequest
之前直接將當前Buffer中的事務性消息(如果有)全部丟棄,但必須等待所有被發送但尚未收到ACK的消息發送完成。
上述第二步是實現將一組讀操作與寫操作作為一個事務處理的關鍵。因為Producer寫入的數據Topic以及記錄Comsumer Offset的Topic會被寫入相同的Transactin Marker
,所以這一組讀操作與寫操作要么全部COMMIT要么全部ABORT。
WriteTxnMarkerRequest
上面提到的WriteTxnMarkerRequest
由Transaction Coordinator
發送給當前事務涉及到的每個<Topic, Partition>
的Leader。收到該請求后,對應的Leader會將對應的COMMIT(PID)
或者ABORT(PID)
控制信息寫入日志,如上圖中步驟5.2所示。
該控制消息向Broker以及Consumer表明對應PID的消息被Commit了還是被Abort了。
這里要注意,如果事務也涉及到__consumer_offsets
,即該事務中有消費數據的操作且將該消費的Offset存於__consumer_offsets
中,Transaction Coordinator
也需要向該內部Topic的各Partition的Leader發送WriteTxnMarkerRequest
從而寫入COMMIT(PID)
或COMMIT(PID)
控制信息。
寫入最終的COMPLETE_COMMIT
或COMPLETE_ABORT
消息
寫完所有的Transaction Marker
后,Transaction Coordinator
會將最終的COMPLETE_COMMIT
或COMPLETE_ABORT
消息寫入Transaction Log
中以標明該事務結束,如上圖中步驟5.3所示。
此時,Transaction Log
中所有關於該事務的消息全部可以移除。當然,由於Kafka內數據是Append Only的,不可直接更新和刪除,這里說的移除只是將其標記為null從而在Log Compact時不再保留。
另外,COMPLETE_COMMIT
或COMPLETE_ABORT
的寫入並不需要得到所有Rreplica的ACK,因為如果該消息丟失,可以根據事務協議重發。
補充說明,如果參與該事務的某些<Topic, Partition>
在被寫入Transaction Marker
前不可用,它對READ_COMMITTED
的Consumer不可見,但不影響其它可用<Topic, Partition>
的COMMIT或ABORT。在該<Topic, Partition>
恢復可用后,Transaction Coordinator
會重新根據PREPARE_COMMIT
或PREPARE_ABORT
向該<Topic, Partition>
發送Transaction Marker
。
4.6 總結
PID
與Sequence Number
的引入實現了寫操作的冪等性- 寫操作的冪等性結合
At Least Once
語義實現了單一Session內的Exactly Once
語義 Transaction Marker
與PID
提供了識別消息是否應該被讀取的能力,從而實現了事務的隔離性- Offset的更新標記了消息是否被讀取,從而將對讀操作的事務處理轉換成了對寫(Offset)操作的事務處理
- Kafka事務的本質是,將一組寫操作(如果有)對應的消息與一組讀操作(如果有)對應的Offset的更新進行同樣的標記(即
Transaction Marker
)來實現事務中涉及的所有讀寫操作同時對外可見或同時對外不可見 - Kafka只提供對Kafka本身的讀寫操作的事務性,不提供包含外部系統的事務性
5 異常處理
5.1 Exception處理
InvalidProducerEpoch
這是一種Fatal Error,它說明當前Producer是一個過期的實例,有Transaction ID
相同但epoch更新的Producer實例被創建並使用。此時Producer會停止並拋出Exception。
InvalidPidMapping
Transaction Coordinator
沒有與該Transaction ID
對應的PID。此時Producer會通過包含有Transaction ID
的InitPidRequest
請求創建一個新的PID。
NotCorrdinatorForGTransactionalId
該Transaction Coordinator
不負責該當前事務。Producer會通過FindCoordinatorRequest
請求重新尋找對應的Transaction Coordinator
。
InvalidTxnRequest
違反了事務協議。正確的Client實現不應該出現這種Exception。如果該異常發生了,用戶需要檢查自己的客戶端實現是否有問題。
CoordinatorNotAvailable
Transaction Coordinator
仍在初始化中。Producer只需要重試即可。
DuplicateSequenceNumber
發送的消息的序號低於Broker預期。該異常說明該消息已經被成功處理過,Producer可以直接忽略該異常並處理下一條消息
InvalidSequenceNumber
這是一個Fatal Error,它說明發送的消息中的序號大於Broker預期。此時有兩種可能
- 數據亂序。比如前面的消息發送失敗后重試期間,新的消息被接收。正常情況下不應該出現該問題,因為當冪等發送啟用時,
max.inflight.requests.per.connection
被強制設置為1,而acks
被強制設置為all。故前面消息重試期間,后續消息不會被發送,也即不會發生亂序。並且只有ISR中所有Replica都ACK,Producer才會認為消息已經被發送,也即不存在Broker端數據丟失問題。 - 服務器由於日志被Truncate而造成數據丟失。此時應該停止Producer並將此Fatal Error報告給用戶。
InvalidTransactionTimeout
InitPidRequest
調用出現的Fatal Error。它表明Producer傳入的timeout時間不在可接受范圍內,應該停止Producer並報告給用戶。
5.2 處理Transaction Coordinator
失敗
5.2.1 寫PREPARE_COMMIT/PREPARE_ABORT
前失敗
Producer通過FindCoordinatorRequest
找到新的Transaction Coordinator
,並通過EndTxnRequest
請求發起COMMIT
或ABORT
流程,新的Transaction Coordinator
繼續處理EndTxnRequest
請求——寫PREPARE_COMMIT
或PREPARE_ABORT
,寫Transaction Marker
,寫COMPLETE_COMMIT
或COMPLETE_ABORT
。
5.2.2 寫完PREPARE_COMMIT/PREPARE_ABORT
后失敗
此時舊的Transaction Coordinator
可能已經成功寫入部分Transaction Marker
。新的Transaction Coordinator
會重復這些操作,所以部分Partition中可能會存在重復的COMMIT
或ABORT
,但只要該Producer在此期間沒有發起新的事務,這些重復的Transaction Marker
就不是問題。
5.2.3 寫完COMPLETE_COMMIT/ABORT
后失敗
舊的Transaction Coordinator
可能已經寫完了COMPLETE_COMMIT
或COMPLETE_ABORT
但在返回EndTxnRequest
之前失敗。該場景下,新的Transaction Coordinator
會直接給Producer返回成功。
5.3 事務過期機制
5.3.1 事務超時
transaction.timeout.ms
5.3.2 終止過期事務
當Producer失敗時,Transaction Coordinator
必須能夠主動的讓某些進行中的事務過期。否則沒有Producer的參與,Transaction Coordinator
無法判斷這些事務應該如何處理,這會造成:
- 如果這種進行中事務太多,會造成
Transaction Coordinator
需要維護大量的事務狀態,大量占用內存 Transaction Log
內也會存在大量數據,造成新的Transaction Coordinator
啟動緩慢READ_COMMITTED
的Consumer需要緩存大量的消息,造成不必要的內存浪費甚至是OOM- 如果多個
Transaction ID
不同的Producer交叉寫同一個Partition,當一個Producer的事務狀態不更新時,READ_COMMITTED
的Consumer為了保證順序消費而被阻塞
為了避免上述問題,Transaction Coordinator
會周期性遍歷內存中的事務狀態Map,並執行如下操作
- 如果狀態是
BEGIN
並且其最后更新時間與當前時間差大於transaction.remove.expired.transaction.cleanup.interval.ms
(默認值為1小時),則主動將其終止:1)未避免原Producer臨時恢復與當前終止流程沖突,增加該Producer對應的PID的epoch,並確保將該更新的信息寫入Transaction Log
;2)以更新后的epoch回滾事務,從而使得該事務相關的所有Broker都更新其緩存的該PID的epoch從而拒絕舊Producer的寫操作 - 如果狀態是
PREPARE_COMMIT
,完成后續的COMMIT流程————向各<Topic, Partition>
寫入Transaction Marker
,在Transaction Log
內寫入COMPLETE_COMMIT
- 如果狀態是
PREPARE_ABORT
,完成后續ABORT流程
5.3 終止Transaction ID
某Transaction ID
的Producer可能很長時間不再發送數據,Transaction Coordinator
沒必要再保存該Transaction ID
與PID
等的映射,否則可能會造成大量的資源浪費。因此需要有一個機制探測不再活躍的Transaction ID
並將其信息刪除。
Transaction Coordinator
會周期性遍歷內存中的Transaction ID
與PID
映射,如果某Transaction ID
沒有對應的正在進行中的事務並且它對應的最后一個事務的結束時間與當前時間差大於transactional.id.expiration.ms
(默認值是7天),則將其從內存中刪除並在Transaction Log
中將其對應的日志的值設置為null從而使得Log Compact可將其記錄刪除。
6 與其它系統事務機制對比
6.1 PostgreSQL MVCC
Kafka的事務機制與《MVCC PostgreSQL實現事務和多版本並發控制的精華》一文中介紹的PostgreSQL通過MVCC實現事務的機制非常類似,對於事務的回滾,並不需要刪除已寫入的數據,都是將寫入數據的事務標記為Rollback/Abort從而在讀數據時過濾該數據。
6.2 兩階段提交
Kafka的事務機制與《分布式事務(一)兩階段提交及JTA》一文中所介紹的兩階段提交機制看似相似,都分PREPARE階段和最終COMMIT階段,但又有很大不同。
- Kafka事務機制中,PREPARE時即要指明是
PREPARE_COMMIT
還是PREPARE_ABORT
,並且只須在Transaction Log
中標記即可,無須其它組件參與。而兩階段提交的PREPARE需要發送給所有的分布式事務參與方,並且事務參與方需要盡可能准備好,並根據准備情況返回Prepared
或Non-Prepared
狀態給事務管理器。 - Kafka事務中,一但發起
PREPARE_COMMIT
或PREPARE_ABORT
,則確定該事務最終的結果應該是被COMMIT
或ABORT
。而分布式事務中,PREPARE后由各事務參與方返回狀態,只有所有參與方均返回Prepared
狀態才會真正執行COMMIT,否則執行ROLLBACK - Kafka事務機制中,某幾個Partition在COMMIT或ABORT過程中變為不可用,只影響該Partition不影響其它Partition。兩階段提交中,若唯一收到COMMIT命令參與者Crash,其它事務參與方無法判斷事務狀態從而使得整個事務阻塞
- Kafka事務機制引入事務超時機制,有效避免了掛起的事務影響其它事務的問題
- Kafka事務機制中存在多個
Transaction Coordinator
實例,而分布式事務中只有一個事務管理器
6.3 Zookeeper
Zookeeper的原子廣播協議與兩階段提交以及Kafka事務機制有相似之處,但又有各自的特點
- Kafka事務可COMMIT也可ABORT。而Zookeeper原子廣播協議只有COMMIT沒有ABORT。當然,Zookeeper不COMMIT某消息也即等效於ABORT該消息的更新。
- Kafka存在多個
Transaction Coordinator
實例,擴展性較好。而Zookeeper寫操作只能在Leader節點進行,所以其寫性能遠低於讀性能。 - Kafka事務是COMMIT還是ABORT完全取決於Producer即客戶端。而Zookeeper原子廣播協議中某條消息是否被COMMIT取決於是否有一大半FOLLOWER ACK該消息。