最近在學習librdkafka,正好翻譯一下他的介紹文檔
第一次翻譯,不到之處請見諒原地址:INTRODUCTION.md
Introduction to librdkafka - the Apache Kafka C/C++ client library
librdkafka 是一個C實現的高性能 Apache Kafka 客戶端,爲生產環境提供了一個可靠和高性能的客戶端。
librdkafka 同樣也提供了傳統的 C++ 接口。
目錄
以下目錄適用於本文
- 性能
- 性能數據
- 高吞吐
- 低延時
- 壓縮
- 消息可靠性
- 用法
- 文檔
- 初始化
- 配置
- 線程和回調
- Brokers
- Producer API
- Consumer API
- 附錄
- 測試詳情
性能
librdkafka 是一個基於現代硬件設計的多線程庫, 並且試圖保持最少的內存拷貝。
如果應用程序願意,生產和消費消息的載體可以不通過任何拷貝實現讓消息大小不受限制。
librdkafka 同樣適用於高吞吐還是低延時的場景,都可以通過屬性配置接口來滿足。
下面是兩個對於性能調節非常重要的屬性:
- batch.num.messages - 發送消息集前,本地隊列等待累計的最小消息數量。
- queue.buffering.max.ms - 等待 batch.num.messages 數量消息填充本地隊列的最長等待時間。
性能數據
接下來的性能測試數據受限於如下配置:
- Intel Quad Core i7 at 3.4GHz, 8GB of memory
- 影響磁盤性能的 brokers 刷新參數配置如下:
log.flush.interval.messages=10000000
log.flush.interval.ms=100000
- 兩個 brokers 運行在和 librdkafka 的同一臺機器上。
- 每個 topic 有兩個 partition。
- 每個 broker 被一個 partition 領導。
- 使用
examples
子目錄下rdkafka_performance
程序。
測試結果
Test1: 2 brokers, 2 partitions, required.acks=2, 100 byte messages:
850000 messages/second, 85 MB/secondTest2: 1 broker, 1 partition, required.acks=0, 100 byte messages:
710000 messages/second, 71 MB/secondTest3: 2 broker2, 2 partitions, required.acks=2, 100 byte messages,
snappy compression:
300000 messages/second, 30 MB/secondTest4: 2 broker2, 2 partitions, required.acks=2, 100 byte messages,
gzip compression:
230000 messages/second, 23 MB/second
提示: 要了解命令的執行等,請參考本文最後的 測試詳情 章節。
提示: 消費者的性能測試將會盡快公佈。
高吞吐
高吞吐的關鍵是消息的批量處理,等待本地隊列累計一定數量的消息,然後用一個大的消息集或批量發送到對端。
通過這種方式補償通訊開銷和消除往返時延(RTT)的不利影響。
默認配置 batch.num.messages=1000 和 queue.buffering.max.ms=1000 適用於高吞吐量場景。
這樣配置允許 librdkafka 等待 1000 ms 讓本地隊列累計 1000 條消息,然後發送累計的消息到 broker。
這些配置是全局的 (rd_kafka_conf_t
),但是基於每一個 topic+partition 上應用。
低延時
當要求發送低延時,“queue.buffering.max.ms”應該調整到儘可能適用於生產側低延時。
設置“queue.buffering.max.ms” 爲 1 來確保消息儘可能快的發送。
你可以查看如何降低消息延時來獲取更多細節。
壓縮
生產者消息壓縮通過配置“compression.codec”屬性生效。
壓縮通過本地隊列批量處理消息實現,批量越大越可能獲得更高的壓縮率。
本地批量隊列大小通過“batch.num.messages”和“queue.buffering.max.ms”配置屬性控制,已經在前面的高吞吐章節描述過了。
消息可靠性
消息可靠性是 librdkafka 的一個重要因素。
通過指定的配置(“request.required.acks”和“message.send.max.retries”,等)應用程序完全可以信賴 librdkafka 來分發消息。
如果 topic 配置屬性“request.required.acks”設置爲等待來自 broker 的消息提交確認(非0,詳見CONFIGURATION.md
),librdkafka 將會等待消直到所有期望的 ack 都收到,並優雅的處理下列事件:
- Broker 連接失敗
- Topic 領導變更
- 來自 broker 的生產者錯誤信號
這些事件都是 librdkafka 自動處理的。對與上面的事件,應用程序無須做任何處理。
失敗消息將會重新發送“message.send.max.retries”次,然後返回失敗報告給應用程序。
發送失敗報告回調函數用於 librdkafka 給應用程序發送消息返回的狀態信號,每個消息發送後的消息狀態報告都會調用一次回調函數:
- 如果
error_code
是非零,那麼消息發送失敗,error_code 表示失敗類型(rd_kafka_resp_err_t
枚舉)。 - 如果
error_code
是零,那麼消息發送成功。
發送報告回調函數的更多使用詳情查看生產者 API。
發送報告回調函數是可配置的。
用法
文檔
librdkafka API 記錄在rdkafka.h
頭文件中,配置屬性記錄在CONFIGURATION.md
中。
初始化
應用程序需要初始化一個頂層對象(rd_kafka_t
)的基礎容器,用於全局配置和共享狀態。
通過調用rd_kafka_new()
創建。
還需要實例化一個或多個 topic(rd_kafka_topic_t
)用於生產或消費。
topic 對象保存 topic 級別的屬性,並且維護一個映射,
該映射保存所有可用 partition 和他們的領導 broker 。
通過調用rd_kafka_topic_new()
創建。
rd_kafka_t
和 rd_kafka_topic_t
都源於可選的配置 API。
不使用該 API 將導致 librdkafka 使用列在文檔CONFIGURATION.md
中的默認配置。
提示:一個應用程序可以創建多個rd_kafka_t
對象,它們不共享狀態。
提示:一個rd_kafka_topic_t
對象只能用於一個rd_kafka_t
對象的創建。
配置
爲了與官方的 Apache Kafka 軟件一致和降低學習門檻,
librdkafka 使用了和 Apache Kafka 官方客戶端完全一致的配置屬性。
在創建對象之前,通過rd_kafka_conf_set()
和 rd_kafka_topic_conf_set()
API
來應用配置。
提示:一旦通過rd_kafka.._new()
使用過,rd_kafka.._conf_t
對象不能再重複使用。
調用rd_kafka.._new()
後,應用程序不需要釋放任何配置資源。
示例
rd_kafka_conf_t *conf;
char errstr[512];
conf = rd_kafka_conf_new();
rd_kafka_conf_set(conf, "compression.codec", "snappy", errstr, sizeof(errstr));
rd_kafka_conf_set(conf, "batch.num.messages", "100", errstr, sizeof(errstr));
rd_kafka_new(RD_KAFKA_PRODUCER, conf);
線程和回調
爲了完全利用的現代硬件,librdkafka 本身支持多線程。
API 是完全線程安全。在任何時間和其任何線程中,應用程序都可以調用任何 API 函數。
一個基於調查的 API 被用於嚮應用程序返回信號。
應用程序需要定期調用rd_kafka_poll()。
這個調查 API 將調用以下配置的回調函數(可選):
- 消息發送報告回調函數 - 標示一個消息被髮送或發送失敗,應用程序可以做出處理貨釋放消息內使用應用資源。
- 錯誤回調函數 - 標示一個錯誤。這些錯誤通常是一個信息類別,比如連接 broker 錯誤,
應用程序通常不需要做任何處理。錯誤的類型在 rd_kafka_resp_err_t 枚舉中指定,
包括遠程 broker 錯誤和本地錯誤。
可選的回調函數不是通過調查出發,它們可能被任何線程調用:
- 日誌回調函數 - 允許應用程序輸出 librdkafka 生成的日誌消息。
- 分區回調函數 - 應用程序提供消息分區的方法。在任何時候任何線程中,分區函數都可能被調用,
並可能被使用同一個鍵值調用多次。
分區函數約束:
- 不得調用任何 rd_kafka_*() 類型函數
- 不得阻塞或執行長時間的函數
- 必須返回一個 0 到 partition_cnt-1 之間的值,
或分區無法執行時返回特殊的 RD_KAFKA_PARTITION_UA 值
Brokers
librdkafka 只需要初始化一個 broker 列表(至少一個)來調用 broker 引導。
librdkafka 會連接所有列在“metadata.broker.list”屬性中或調用rd_kafka_brokers_add()
添加的 broker 引導,並且查詢列表中每一的元數據信息,包括 broker、topic、partition 和 kafka 集羣的領導者。
Broker 名字類似於“host[:port]”,其中端口是可選的(默認 9092),host是可用的主機名或IPv4、IPv6地址。
如果是一個複雜地址,librdkafka會循環地址嘗試連接。
一個 DNS 記錄用於包含所有可用於引導的 broker 地址。
新特性
Apache Kafka broker 版本 0.10.0 新增了一個 ApiVersionRequest API,允許客戶端查詢 broker 支持的 API 版本。
librdkafka 支持這個特性,會查詢每一個 broker 獲取該信息(如果api.version.request=true
),根據該信息生效或失效各種特性,如MessageVersion 1 (timestamps), KafkaConsumer等。
如果 broker 沒有對 librdkafka 的 ApiVersionRequest 請求作出正確響應,會認爲 broker 版本太老不支持該 API,並回退到老版本 broker 的 API。回退的版本可配置到 librdkafka 中,通過broker.version.fallback
屬性控制。
Producer API
通過RD_KAFKA_PRODUCER
類型設置好rd_kafka_t
對象後,一個或多個rd_kafka_topic_t
對象就準備好接收消息,並組裝和發送到 broker。
rd_kafka_produce()
函數接受如下參數:
rkt
- 生產的 topic,之前通過rd_kafka_topic_new()
生成partition
- 生產的 partition。如果設置爲RD_KAFKA_PARTITION_UA
(未賦值的),需要通過配置分區函數去選擇一個確定 partition。msgflags
- 0 或下面的值:RD_KAFKA_MSG_F_COPY
- librdkafka 將立即從 payload 做一份拷貝。如果 payload 是不穩定存儲,如棧,需要使用這個參數。RD_KAFKA_MSG_F_FREE
- 當 payload 使用完後,讓 librdkafka 使用free(3)
釋放。
這兩個標誌互斥,如果都不設置,payload 既不會被拷貝也不會被 librdkafka 釋放。
如果
RD_KAFKA_MSG_F_COPY
標誌不設置,就會有數據拷貝,librdkafka 將佔用 payload 指針直到消息被髮送或失敗。
librdkafka 處理完消息後,會調用發送報告回調函數,讓應用程序重新獲取 payload 的所有權。
如果設置了RD_KAFKA_MSG_F_FREE
,應用程序就不要在發送報告回調函數中釋放 payload。payload
,len
- 消息 payloadkey
,keylen
- 可選的消息鍵,用於分區。將會用於 topic 分區回調函數,如果有,會附加到消息中發送給 broker。msg_opaque
- 可選的,應用程序爲每個消息提供的無類型指針,提供給消息發送回調函數,用於應用程序引用。
rd_kafka_produce()
是一個非阻塞 API,該函數會將消息塞入一個內部隊列並立即返回。
如果隊列中的消息數超過queue.buffering.max.messages
屬性配置的值,rd_kafka_produce()
通過返回 -1,並在ENOBUFS
中設置錯誤碼來反饋錯誤。
提示: 見 examples/rdkafka_performance.c
獲取生產者的使用。
Simple Consumer API (legacy)
提示:要獲取 high-level KafkaConsumer 接口,查看 rd_kafka_subscribe (rdkafka.h) 或 KafkaConsumer (rdkafkacpp.h)
消費者 API 比生產者 API 更復雜。
通過RD_KAFKA_CONSUMER
類型創建rd_kafka_t
對象並實例化rd_kafka_topic_t
後,應用程序還需要調用rd_kafka_consume_start()
指定partition。
rd_kafka_consume_start()
參數:
rkt
- 要消費的 topic ,之前通過rd_kafka_topic_new()
創建。partition
- 要消費的 partition。offset
- 消費開始的消息偏移量。可以是絕對的值或兩中特殊的偏移量:
RD_KAFKA_OFFSET_BEGINNING
從該 partition 的隊列的最開始消費(最老的消息)。RD_KAFKA_OFFSET_END
從該 partition 產生的下一個消息開始消費。RD_KAFKA_OFFSET_STORED
使用偏移量存儲。
當一個 topic+partition 消費者被啓動,librdkafka 不斷嘗試從 broker 批量獲取消息來保持本地隊列有queued.min.messages
數量的消息。
本地消息隊列通過 3 個不同的消費 API 嚮應用程序提供服務:
rd_kafka_consume()
- 消費一個消息rd_kafka_consume_batch()
- 消費一個或多個消息rd_kafka_consume_callback()
- 消費本地隊列中的所有消息,且每一個都調用回調函數
這三個 API 的性能按照升序排列,rd_kafka_consume()
最慢,rd_kafka_consume_callback()
最快。不同的類型滿足不同的應用需要。
被上述函數消費的消息通過rd_kafka_message_t
類型返回。
rd_kafka_message_t
的成員:
* err
- 返回給應用程序的錯誤信號。如果該值不是零,payload
字段應該是一個錯誤的消息,err
是一個錯誤碼(rd_kafka_resp_err_t
)。
* rkt
,partition
- 消息的 topic 和 partition 或錯誤。
* payload
,len
- 消息的數據或錯誤的消息 (err!=0)。
* key
,key_len
- 可選的消息鍵,生產者指定。
* offset
- 消息偏移量。
不管是payload
和key
的內存,還是整個消息,都由 librdkafka 所擁有,且在rd_kafka_message_destroy()
被調用後不要使用。
librdkafka 爲了避免消息集的多餘拷貝,會爲所有從內存緩存中接收的消息共享同一個消息集,這意味着如果應用程序保留單個rd_kafka_message_t
,將會阻止內存釋放並用於同一個消息集的其他消息。
當應用程序從一個 topic+partition 中消費完消息,應該調用rd_kafka_consume_stop()
來結束消費。該函數同時會清空當前本地隊列中的所有消息。
提示: 見 examples/rdkafka_performance.c
獲取消費者的使用。
偏移量管理
當 broker 版本大於等於 0.9.0 時,基於 broker 的偏移量管理可通過使用 high-level KafkaConsumer 接口(見rdkafka.h 或 rdkafkacpp.h)實現。
偏移量管理同樣也可以通過本地偏移量文件存儲,按每一個topic+partition,偏移量定時寫入本地文件。通過下列 topic 屬性配置:
auto.commit.enable
auto.commit.interval.ms
offset.store.path
offset.store.sync.interval.ms
當前還不支持 zookeeper 的偏移量管理。
消費者組
基於消費者組的 broker 是支持的(要求 Apache Kafka broker >=0.9)。見 rdkafka.h 或 rdkafkacpp.h 中的 KafkaConsumer。
Topics
Topic 自動創建
librdkafka 支持自動創建 topic。broker 需要配置auto.create.topics.enable=true
。
附錄
測試詳情
Test1: Produce to two brokers, two partitions, required.acks=2, 100 byte messages
兩個partition,每個broker領導其中一個。分區方法默認隨機,每個 broker 上的 partition 分配大約 250000 條消息。
Command:
# examples/rdkafka_performance -P -t test2 -s 100 -c 500000 -m "_____________Test1:TwoBrokers:500kmsgs:100bytes" -S 1 -a 2
....
% 500000 messages and 50000000 bytes sent in 587ms: 851531 msgs/s and 85.15 Mb/s, 0 messages failed, no compression
Result:
Message transfer rate is approximately 850000 messages per second,
85 megabytes per second.
Test2: Produce to one broker, one partition, required.acks=0, 100 byte messages
Command:
# examples/rdkafka_performance -P -t test2 -s 100 -c 500000 -m "_____________Test2:OneBrokers:500kmsgs:100bytes" -S 1 -a 0 -p 1
....
% 500000 messages and 50000000 bytes sent in 698ms: 715994 msgs/s and 71.60 Mb/s, 0 messages failed, no compression
Result:
Message transfer rate is approximately 710000 messages per second,
71 megabytes per second.
Test3: Produce to two brokers, two partitions, required.acks=2, 100 byte messages, snappy compression
Command:
# examples/rdkafka_performance -P -t test2 -s 100 -c 500000 -m "_____________Test3:TwoBrokers:500kmsgs:100bytes:snappy" -S 1 -a 2 -z snappy
....
% 500000 messages and 50000000 bytes sent in 1672ms: 298915 msgs/s and 29.89 Mb/s, 0 messages failed, snappy compression
Result:
Message transfer rate is approximately 300000 messages per second,
30 megabytes per second.
Test4: Produce to two brokers, two partitions, required.acks=2, 100 byte messages, gzip compression
Command:
# examples/rdkafka_performance -P -t test2 -s 100 -c 500000 -m "_____________Test3:TwoBrokers:500kmsgs:100bytes:gzip" -S 1 -a 2 -z gzip
....
% 500000 messages and 50000000 bytes sent in 2111ms: 236812 msgs/s and 23.68 Mb/s, 0 messages failed, gzip compression
Result:
Message transfer rate is approximately 230000 messages per second,
23 megabytes per second.