Linux下kafka之C/C++用戶端庫librdkafka的編譯,安裝以及函數介紹

來源:互聯網
上載者:User

https://github.com/edenhill/librdkafka
librdkafka是一個開源的Kafka用戶端C/C++實現,提供了Kafka生產者、消費者介面。

一、安裝librdkafka
首先在github上下載librdkafka源碼,解壓後進行編譯;
cd librdkafka-master
chmod 777 configure lds-gen.py
 ./configure
make
make install
在make的時候,如果是64位Linux會報下面這個異常
/bin/ld:librdkafka.lds:1: syntax error in VERSION script
只要Makefile.config第46行裡面的WITH_LDS=y這一行注釋掉就不會報錯了。

注釋掉:#WITH_LDS=y,然後再make

最終標頭檔和庫檔案會分別安裝在

/usr/local/include/librdkafka
/usr/local/lib


二、調用librdkafka庫自主編程

編譯使用者自己的應用程式,編譯選項要加上-lrdkafka -lz -lpthread -lrt這些選項。

例如,我使用QtCreator之qmake模式,.pro檔案如下:

QMAKE_LFLAGS += -lrdkafka -lrdkafka++ -lz -lpthread -lrt#-lrdkafka等價於 LIBS += /usr/local/lib/librdkafka.so
編譯通過,但是運行時會報錯:error while loading shared libraries: librdkafka.so.1: cannot open shared object file: No such file or directory
此時需要在/etc/ld.so.conf中加入librdkafka.so所在的目錄:/usr/local/lib/
然後在終端執行命令,使之生效:

[root@localhost etc]# ldconfig

注意,/usr/local/lib/每次有庫檔案更新,都需要終端重新運行一次ldconfig這條命令。

三、啟動kafka

詳情參考我的文章:我個人的kafka_2.12-1.0.0實踐:安裝與測試(★firecat推薦★)


四、用法介紹,源檔案來自/librdkafka-master/examples/rdkafka_example.cpp和rdkafka_consumer_example.cpp

Producer的使用方法:

建立kafka用戶端配置預留位置: 
conf = rd_kafka_conf_new();即建立一個設定物件(rd_kafka_conf_t)。並通過rd_kafka_conf_set進行brokers的配置。

設定資訊的回調: 
用以反饋資訊發送的成敗。通過rd_kafka_conf_set_dr_msg_cb(conf, dr_msg_cb);實現。

建立producer執行個體: 
1)初始化: 
應用程式需要初始化一個頂層對象(rd_kafka_t)的基礎容器,用於全域配置和共用狀態。 
通過調用rd_kafka_new()建立。建立之後,該執行個體就佔有了conf對象,所以conf對象們在rd_kafka_new()調用之後是不能被再次使用的,而且在rd_kafka_new()調用之後也不需要釋放配置資源的。 
2)建立topic: 
建立的topi對象是可以複用的(producer的執行個體化對象(rd_kafka_t)也是允許複用的,所以這兩者就沒有必要頻繁建立) 
執行個體化一個或多個 topic(rd_kafka_topic_t)用於生產或消費。 
topic 對象儲存 topic 層級的屬性,並且維護一個映射, 
該映射儲存所有可用 partition 和他們的領導 broker 。 
通過調用rd_kafka_topic_new()建立(rd_kafka_topic_new(rk, topic, NULL);)。 
註:rd_kafka_t 和 rd_kafka_topic_t都源於可選的配置 API。 
不使用該 API 將導致 librdkafka 使用列在文檔CONFIGURATION.md中的預設配置。

3)Producer API: 
通過調用RD_KAFKA_PRODUCER設定一個或多個rd_kafka_topic_t對象,就可以準備好接收訊息,並組裝和發送到 broker。 
rd_kafka_produce()函數接受如下參數: 
rkt : 待生產的topic,之前通過rd_kafka_topic_new()產生 
partition : 生產的 partition。如果設定為RD_KAFKA_PARTITION_UA(未賦值的),則會根據builtin partitioner去選擇一個確定 partition。kafka會回調partitioner進行均衡選取,partitioner方法需要自己實現。可以輪詢或者傳入key進行hash。未實現則採用預設的隨機方法rd_kafka_msg_partitioner_random隨機播放。 
可以嘗試通過partitioner來設計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 : 訊息 payload(message payload,即值),訊息長度 
key,keylen : 可選的訊息鍵及其長度,用於分區。將會用於 topic 分區回呼函數,如果有,會附加到訊息中發送給 broker。 
msg_opaque : 可選的,應用程式為每個訊息提供的無類型指標,提供給訊息發送回呼函數,用於應用程式引用。

rd_kafka_produce() 是一個非阻塞 API,該函數會將訊息塞入一個內部隊列並立即返回。 
如果隊列中的訊息數超過queue.buffering.max.messages屬性配置的值,rd_kafka_produce()通過返回 -1,並將errno設定為ENOBUFS這樣的錯誤碼來反饋錯誤。 
提示: 見 examples/rdkafka_performance.c 擷取生產者的使用。 Consumer的使用方法:

consumer API要比producer API多一些狀態。 在使用RD_KAFKA_CONSUMER類型(調用rd_kafka_new時設定的函數參數)建立rd_kafka_t 對象,再通過調用rd_kafka_brokers_add對上述new出來的Kafka handle(rk)進行broker的添加(rd_kafka_brokers_add(rk, brokers)), 
然後建立rd_kakfa_topic_t對象之後,

rd_kafka_query_watermark_offsets

建立topic: 
rtk = rd_kafka_topic_new(rk, topic, topic_conf)

開始消費: 
調用rd_kafka_consumer_start()函數(rd_kafka_consume_start(rkt, partition, start_offset))啟動對給定partition的consumer。 
調用rd_kafka_consumer_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 擷取消費者的使用。

在Kafka broker中server.properties檔案配置(參數log.dirs=/data2/logs/kafka/)使得寫入到訊息佇列中的topic在該目錄下對分區的形式進行儲存。每個分區partition下是由segment file組成,而segment file包括2大部分:分別為index file和data file,此2個檔案一一對應,成對出現,尾碼”.index”和“.log”分別表示為segment索引檔案、資料檔案。 
segment檔案命名規則:partion全域的第一個segment從0開始,後續每個segment檔案名稱為上一個segment檔案最後一條訊息的offset值。數值最大為64位long大小,19位元字字元長度,沒有數字用0填充。 具體樣本:

本文所採用的是cpp方式,和上述介紹的只是函數使用上的不同,商務邏輯是一樣的。 
在producer過程中直接是使用PARTITION_UA 但是在消費的時候,不能夠指定partition值為PARTITION_UA因為該值其實是-1,對於Consumer端來說,是無意義的。根據源碼可以知道當不指定partitioner的時候,其實是有一個預設的partitioner,就是Consistent-Random partitioner所謂的一致性隨機partitioner。一致性hash對關鍵字進行map映射之後到一個特定的partition。 
函數原型:

rd_kafka_msg_partitioner_consistent_random (           const rd_kafka_topic_t *rkt,           const void *key, size_t keylen,           int32_t partition_cnt,           void *opaque, void *msg_opaque);

PARTITION_UA其實是Unassigned partition的意思,即是未賦值的分區。RD_KAFKA_PARTITION_UA (unassigned)其實是自動採用topic下的partitioner函數,當然也可以直接採用固定的值。 
在設定檔config/server.properties中是可以設定partition的數量num.partitions。 分配分區

在分配分區的時候,要注意。對於一個已經建立了分區的主題且已經指定了分區,那麼之後的producer代碼如果是直接修改partitioner部分的代碼,直接引入key值進行分區的重新分配的話,是不行的,會繼續按照之前的分區進行添加(之前的分區是分區0,只有一個)。此時如果在程式中查看partition_cnt我們是可以看到,該值並沒有因為config/server.properties的修改而變化,這是因為此時的partition_cnt是針對該已經建立的主題topic的。 
而如果尚自單純修改代碼中的partition_cnt在用於計算分區值時候:djb_hash(key->c_str(), key->size()) % 5 是會得到如下結果的:提示分區不存在。 
 
我們可以通過rdkafka_example來查看某個topic下對應的partition數量的: 
./rdkafka_example -L -t helloworld_kugou -b localhost:9092


從中我們可以看到helloworld_kugou主題只有一個partition,而helloworld_kugou1主題是有5個partition的,這個和我們預期的相符合。 
我們可以對已經建立的主題修改其分區: 
./bin/kafka-topics.sh --zookeeper 127.0.0.1:2181 --alter --partition 5 --topic helloworld_kugou 
修改完之後,我們可以看出,helloworld_kugou已經變為5個分區了。 

具體樣本:

建立topic為helloworld_kugou_test,5個partition。我們可以看到,在producer端進行輸入之前,在預先設定好的log目錄下是已經有5個partition: 

producer端代碼:

class ExampleDeliveryReportCb : public RdKafka::DeliveryReportCb { public:  void dr_cb (RdKafka::Message &message) {    std::cout << "Message delivery for (" << message.len() << " bytes): " <<        message.errstr() << std::endl;    if (message.key())      std::cout << "Key: " << *(message.key()) << ";" << std::endl;  }};class ExampleEventCb : public RdKafka::EventCb { public:  void event_cb (RdKafka::Event &event) {    switch (event.type())    {      case RdKafka::Event::EVENT_ERROR:        std::cerr << "ERROR (" << RdKafka::err2str(event.err()) << "): " <<            event.str() << std::endl;        if (event.err() == RdKafka::ERR__ALL_BROKERS_DOWN)          run = false;        break;      case RdKafka::Event::EVENT_STATS:        std::cerr << "\"STATS\": " << event.str() << std::endl;        break;      case RdKafka::Event::EVENT_LOG:        fprintf(stderr, "LOG-%i-%s: %s\n",                event.severity(), event.fac().c_str(), event.str().c_str());        break;      default:        std::cerr << "EVENT " << event.type() <<            " (" << RdKafka::err2str(event.err()) << "): " <<            event.str() << std::endl;        break;    }  }};/* Use of this partitioner is pretty pointless since no key is provided * in the produce() call.so when you need input your key */class MyHashPartitionerCb : public RdKafka::PartitionerCb {    public:        int32_t partitioner_cb (const RdKafka::Topic *topic, const std::string *key,int32_t partition_cnt, void *msg_opaque)         {            std::cout<<"partition_cnt="<<partition_cnt<<std::endl;            return djb_hash(key->c_str(), key->size()) % partition_cnt;        }    private:        static inline unsigned int djb_hash (const char *str, size_t len)         {        unsigned int hash = 5381;        for (size_t i = 0 ; i < len ; i++)            hash = ((hash << 5) + hash) + str[i];        std::cout<<"hash1="<<hash<<std::endl;        return hash;        }};void TestProducer(){    std::string brokers = "localhost";    std::string errstr;    std::string topic_str="helloworld_kugou_test";//自行制定主題topic    MyHashPartitionerCb hash_partitioner;    int32_t partition = RdKafka::Topic::PARTITION_UA;    int64_t start_offset = RdKafka::Topic::OFFSET_BEGINNING;    
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.