本文基于Spark2.1.0、Kafka 0.10.2、Scala 2.11.8版本
背景:
Kafka做為一款流行的分布式發布訂閱消息系統,以高吞吐、低延時、高可靠的特點著稱,已經成為Spark Streaming常用的流數據來源。
1,Kafka Topic的消費保證:
流數據,顧名思義,就是無界的、流動的、快速的、連續到達的數據序列,所以它不像可靠的文件系統(如HDFS)在計算出現故障時,可以隨時恢復數據來重新計算。
那么,如何保證流數據可靠的傳遞呢?我們先了解下面的概念:
Producer通過Broker傳遞消息給Consumer,Consumer消費消息,
P-B-C 3者之間的傳輸,主要有以下幾種可能的場景:
At most once(最多傳輸一次): 消息可能會丟,但絕不會重復傳輸;
At least one ?(至少傳輸一次): ?消息絕不會丟,但可能會重復傳輸;
Exactly once(精確傳輸一次): ?每條消息肯定會被傳輸一次且僅傳輸一次,不會重復.
3種場景,適合不同生產環境的需要,相關介紹網上很多,這里就不多說了。
本文的重點是,如何用Spark 2.1.0、Kafka 0.10.2、spark-streaming-kafka-0-10_2.11-2.1.0.jar、HBase 1.3.0來配合實現消息Exactly once(精確一次)的傳遞和消費。網上相關的scala或者java代碼,都是基于老版本的API,目前沒有發現基于new Kafka consumer API的實現,所以看到本文覺得有收獲的同學,就給點個贊吧。
2,Kafka 0.10.2版本介紹:
Kafka 0.10.2版本,為了和Zookeeper的解耦,較之前的版本有了很大的變化,老版本的高級API和簡單API的說法不見了,取而代之的是New Consumer API及New Consumer Configs,相關接口的參數及P-B-C 3者的配置參數有了很多改動。
Spark官方與之配合的工具包spark-streaming-kafka-0-10_2.11-2.1.0.jar 也做了相應的改變,取消了KafkaCluster類、取消了ZkUtils.updatePersistentPath等多個方法,也都是為了不在將Topic offset由zookeeper自動保存,而由用戶靈活的選擇Kafka和Spark 2.1.0官方提供的幾種方法來保存offset,最好的使用情況下,端到端的業務可以達到精確一次的消費保證。
(為了美觀,本文相關的java代碼都用貼圖方式展現了,最終實現的端到端精確一次消費消息的源碼見文末的鏈接)
3,Kafka官方提供的多種消費保證:
Consumer的3個重要的配置,需要配合使用,來達到Broker到Consumer之間精確一次的消費保證。
請看這些參數的組合(有點繞,請仔細看)
(enable.auto.commit:false) + (auto.offset.reset:earliest):
在Broker到Consumer之間實現了至少一次語義,因為不使用Kafka提供的自動保存offset功能,每次應用程序啟動時,都是從Topic的初始位置來獲取消息。也就是說,應用程序因為故障失敗,或者是人為的停止,再次啟動應用程序時,都會從初始位置把指定的Topic所有的消息都消費一遍,這就導致了Consumer會重復消費。
(enable.auto.commit:false) + (auto.offset.reset:latest):
在Broker到Consumer之間實現了至多一次語義,因為不使用Kafka提供的自動保存offset功能,每次應用程序啟動時,都是從Topic的末尾位置來獲取消息。也就是說,應用程序因為故障失敗,或者是人為的停止后,如果Producer向Broker發送新的消息,當再次啟動應用程序時,Consumer從指定的Topic的末尾來開始消費,這就導致了這部分新產生的消息丟失。
(enable.auto.commit:true)+(auto.offset.reset:earliest)+(auto.commit.interval.ms) :
在Broker到Consumer之間實現了精確一次語義,因為使用了Kafka提供的自動保存offset功能,當應用程序第一次啟動時,首先從Topic的初試位置來獲取消息,原有的消息一個都沒有丟失;緊接著,在auto.commit.interval.ms時間后,Kafka會使用coordinator協議commit當前的offset(topic的每個分區的offset)。當應用程序因為故障失敗,或者是人為的停止,再次啟動應用程序時,都會從coordinator模塊獲取Topic的offset,從上一次消費結束的位置繼續消費,所以不會重復消費已經消費過的消息,也不會丟失在應用程序停止期間新產生的消息,做到了Broker到Consumer之間精確一次的傳遞。
下面是Kafka 0.10.2 ConsumerCoordinator.java的源碼片段,用戶配置enable.auto.commit:true對應的代碼是autoCommitEnabled為true,最終調用doAutoCommitOffsetsAsync,使用coordinator協議保存offset(注意,最新版本已經和zookeeper解耦,不會把offset保存在zookeeper中,所以通過zkCli.sh是看不到相關topic的)
下面是實現的Spark Streaming代碼。
當然,這還遠遠不夠,因為這樣的方式,會出現業務兩段性的后果:
1,讀完消息先commit再處理消息。這種模式下,如果consumer在commit后還沒來得及處理消息就crash了,下次重新開始工作后就無法讀到剛剛已提交而未處理的消息,這就對應于At most once;
2,讀完消息先處理再commit。這種模式下,如果處理完了消息在commit之前consumer crash了,下次重新開始工作時還會處理剛剛未commit的消息,實際上該消息已經被處理過了。這就對應于At least once。
所以,要想實現端到端消息的精確一次消費,還需要耐心往后看。
3,Spark官方提供的多種消費保證:(基于spark-streaming-kafka-0-10_2.11-2.1.0.jar,相比前一個版本有很多改變)
CheckPoint:
通過設置Driver程序的checkpoint,來保存topic offset。這種方法很簡單,但是缺陷也很大:應用程序有改變時,無法使用原來的checkpoint來恢復offset;只能滿足Broker到Consumer之間精確一次的傳遞。
當應用程序第一次啟動時,首先從Topic的初試位置來獲取消息,原有的消息一個都沒有丟失;緊接著,在batch時間到達后,Spark會使用checkpoint保存當前的offset(topic的每個分區的offset)。當應用程序失敗或者人為停止后,再次啟動應用程序時,都會從checkpoint恢復Topic的offset,從上一次消費結束的位置繼續消費,所以不會重復消費已經消費過的消息,也不會丟失在應用程序停止期間新產生的消息。
實現的Spark Streaming代碼如下(注意:Spark 1.6.3之后,檢查checkpoint的實現已經不在用JavaStreamingContextFactory工廠操作了,請細看我的代碼是怎么做的)
Kafka itself:
和前面提到的enable.auto.commit:true異曲同工,不過這里用commitAsync方法異步的把offset提交給Kafka 。當應用程序第一次啟動時,首先從Topic的初試位置來獲取消息,原有的消息一個都沒有丟失;緊接著,用commitAsync方法異步的把offset提交給Kafka(topic的每個分區的offset)。當應用程序失敗或者人為停止后,再次啟動應用程序時,都會從kafka恢復Topic的offset,從上一次消費結束的位置繼續消費,所以不會重復消費已經消費過的消息,也不會丟失在應用程序停止期間新產生的消息。
與checkpoint相比,應用程序代碼的更改不會影響offset的存儲和獲取。然而,這樣的操作不是事務性的,由于是異步提交offset,當提交offset過程中應用程序crash,則無法保存正確的offset,會導致消息丟失或者重復消費。
實現的Spark Streaming代碼如下:
Your own data store:(當當當當,好戲出場)
如果要做到消息端到端的Exactly once消費,就需要事務性的處理offset和實際操作的輸出。
經典的做法讓offset和操作輸出存在同一個地方,會更簡潔和通用。比如,consumer把最新的offset和加工后的數據一起寫到HBase中,那就可以保證數據的輸出和offset的更新要么都成功,要么都失敗,間接實現事務性,最終做到消息的端到端的精確一次消費。(新版本的官網中只字未提使用Zookeeper保存offset,是有多嫌棄??)
實現的Spark Streaming代碼如下(ConsumerRecord類不能序列化,使用時要注意,不要分發該類到其他工作節點上,避免錯誤打印)
其實說白了,官方提供的思路就是,把JavaInputDStream轉換為OffsetRange對象,該對象具有topic對應的分區的所有信息,每次batch處理完,Spark Streaming都會自動更新該對象,所以你只需要找個合適的地方保存該對象(比如HBase、HDFS),就可以愉快的操縱offset了。
4,相關鏈接
Spark Streaming + Kafka Integration Guide (Kafka broker version 0.10.0 or higher)
(如需轉載,請標明作者和出處)