Kafka分布式的單位是partition,同一個partition用一個write ahead log組織,所以可以保證FIFO的順序。不同partition之間不能保證順序。
但是絕大多數用戶都可以通過message key來定義,因為同一個key的message可以保證只發送到同一個partition,比如說key是user id,table row id等等,所以同一個user或者同一個record的消息永遠只會發送到同一個partition上,保證了同一個user或record的順序。當然,如果你有key skewness 就有些麻煩,需要特殊處理
Apache Kafka官方保證了partition內部的數據有效性(追加寫、offset讀);為了提高Topic的並發吞吐能力,可以提高Topic的partition數,並通過設置partition的replica來保證數據高可靠;
但是在多個Partition時,不能保證Topic級別的數據有序性。
因此,如果你們就像死磕kafka,但是對數據有序性有嚴格要求,那我建議:
- 創建Topic只指定1個partition,這樣的壞處就是磨滅了kafka最優秀的特性。
所以可以思考下是不是技術選型有問題, kafka本身適合與流式大數據量,要求高吞吐,對數據有序性要求不嚴格的場景。
原文鏈接:http://www.lpnote.com/2017/01/17/sequence-message-in-kafka/
順序消息包括以下兩方面:
- 全局順序
- 局部順序
全局順序
全局順序就目前的應用范圍來講,可以列舉出來的也就限於binlog日志傳輸,如mysql binlog日志傳輸要求全局的順序,不能有任何的亂序。這種的解決辦法通常是最為保守的方式:
- 全局使用一個生產者
- 全局使用一個消費者(並嚴格到一個消費線程)
- 全局使用一個分區(當然不同的表可以使用不同的分區或者topic實現隔離與擴展)
局部順序
其實在大部分業務場景下,只需要保證消息局部有序即可,什么是局部有序?局部有序是指在某個業務功能場景下保證消息的發送和接收順序是一致的。如:訂單場景,要求訂單的創建、付款、發貨、收貨、完成消息在同一訂單下是有序發生的,即消費者在接收消息時需要保證在接收到訂單發貨前一定收到了訂單創建和付款消息。
針對這種場景的處理思路是:針對部分消息有序(message.key相同的message要保證消費順序)場景,可以在producer往kafka插入數據時控制,同一key分發到同一partition上面。因為每個partition是固定分配給某個消費者線程進行消費的,所以對於在同一個分區的消息來說,是嚴格有序的(在kafka 0.10.x以前的版本中,kafka因消費者重啟或者宕機可能會導致分區的重新分配消費,可能會導致亂序的發生,0.10.x版本進行了優化,減少重新分配的可能性)。
注意事項
消息重試對順序消息的影響
對於一個有着先后順序的消息A、B,正常情況下應該是A先發送完成后再發送B,但是在異常情況下,在A發送失敗的情況下,B發送成功,而A由於重試機制在B發送完成之后重試發送成功了。
這時對於本身順序為AB的消息順序變成了BA
消息producer發送邏輯的控制
消息producer在發送消息的時候,對於同一個broker連接是存在多個未確認的消息在同時發送的,也就是存在上面場景說到的情況,雖然A和B消息是順序的,但是由於存在未知的確認關系,有可能存在A發送失敗,B發送成功,A需要重試的時候順序關系就變成了BA。簡之一句就是在發送B時A的發送狀態是未知的。
針對以上的問題,嚴格的順序消費還需要以下參數支持:max.in.flight.requests.per.connection
這個參數官方文檔的解釋是:
The maximum number of unacknowledged requests the client will send on a single connection before blocking. Note that if this setting is set to be greater than 1 and there are failed sends, there is a risk of message re-ordering due to retries (i.e., if retries are enabled).
大體意思是:
在發送阻塞前對於每個連接,正在發送但是發送狀態未知的最大消息數量。如果設置大於1,那么就有可能存在有發送失敗的情況下,因為重試發送導致的消息亂序問題。
所以我們應該將其設置為1,保證在后一條消息發送前,前一條的消息狀態已經是可知的。