• 转载:kafka c接口librdkafka介绍之二:生产者接口


    转载:from:http://www.verydemo.com/demo_c92_i210679.html

    这个程序虽然我调试过,也分析过,但是没有记录笔记,发现下边这篇文章分析直接透彻,拿来借用,聊以自省,开发一直要研究透彻源码,学习其中的处理手段!

    kafka c接口librdkafka介绍之二:生产者接口

    一、librdkafka基本信息:
     源码结构:
    kafka/
    ├── examples
    │   ├── Makefile
    │   ├── rdkafka_example
    │   ├── rdkafka_example.c
    │   ├── rdkafka_performance
    │   └── rdkafka_performance.c
    ├── librdkafka.a
    ├── librdkafka.conf
    ├── librdkafka.so
    ├── librdkafka.so.0 -> librdkafka.so
    ├── LICENSE
    ├── LICENSE.pycrc
    ├── Makefile
    ├── rdaddr.c
    ├── rdaddr.d
    ├── rdaddr.h
    ├── rdaddr.o
    ├── rd.c
    ├── rdcrc32.c
    ├── rdcrc32.d
    ├── rdcrc32.h
    ├── rdcrc32.o
    ├── rdfile.c
    ├── rdfile.d
    ├── rdfile.h
    ├── rdfile.o
    ├── rdgz.c
    ├── rdgz.d
    ├── rdgz.h
    ├── rdgz.o
    ├── rd.h
    ├── rdkafka.c
    ├── rdkafka.d
    ├── rdkafka.h
    ├── rdkafka.o
    ├── rdrand.c
    ├── rdrand.d
    ├── rdrand.h
    ├── rdrand.o
    ├── rdtime.h
    ├── rdtypes.h
    ├── README.md
    └── rpm
        ├── librdkafka-build.sh
        ├── librdkafka.spec.in
        └── librdkafka-VER.txt

    文件数:
    find . -name "*.[h|c]" | wc -l
    18

    代码行数:
    find . -name "*.[h|c]" | xargs wc -l
        89 ./rdtime.h
       246 ./examples/rdkafka_example.c
       351 ./examples/rdkafka_performance.c
       109 ./rd.h
        47 ./rdtypes.h
       183 ./rdaddr.h
        37 ./rd.c
       201 ./rdaddr.c
       139 ./rdfile.c
        42 ./rdgz.h
       103 ./rdcrc32.h
        50 ./rdrand.c
       121 ./rdgz.c
        45 ./rdrand.h
       520 ./rdkafka.h
      1334 ./rdkafka.c
        88 ./rdfile.h
       133 ./rdcrc32.c
      3838 total

    核心代码:
    rdkafka.h
    rdkafka.c

    二、生产者接口分析
     librdkafka 生产者接口支持应用程序向kafka推送数据,这可以满足一大类通过网络向kafka直接传送数据的需求。
     推送流程:
    这里通过librdkafka自带的例子来说明(例子可参考前文kafka c接口librdkafka介绍之一)
    发送的关键两步:
    step 1)  创建kafka实例

    点击(此处)折叠或打开

    1. rk = rd_kafka_new(RD_KAFKA_PRODUCER, broker, NULL)
    step 2)发送数据

    点击(此处)折叠或打开

    1. rd_kafka_produce(rk, topic, partition, RD_KAFKA_OP_F_FREE, opbuf, len)

    下面就看看这两步背后librdkafka做了什么?
    先看看rd_kafka_new做了什么

    点击(此处)折叠或打开

    1. rd_kafka_t *rd_kafka_new (rd_kafka_type_t type, const char *broker,
    2.              const rd_kafka_conf_t *conf) {
    3.     rd_kafka_t *rk;
    4.     rd_sockaddr_list_t *rsal;
    5.     const char *errstr;
    6.     static int rkid = 0;
    7.     int err;
    8.     /* If broker is NULL, default it to localhost. */
    9.     if (!broker)
    10.         broker = "localhost";
    11.     
    12.     /* 获取broker的地址信息 */
    13.     if (!(rsal = rd_getaddrinfo(broker, RD_KAFKA_PORT_STR, AI_ADDRCONFIG,
    14.                  AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
    15.                  &errstr))) {
    16.         rd_kafka_log(NULL, LOG_ERR, "GETADDR",
    17.              "getaddrinfo failed for '%s': %s",
    18.              broker, errstr);
    19.         return NULL;
    20.     }
    21.             
    22.     /*
    23.      * Set up the handle.
    24.      */
    25.     rk = calloc(1, sizeof(*rk));
    26.     rk->rk_type = type;
    27.     rk->rk_broker.rsal = rsal;
    28.     rk->rk_broker.s = -1;
    29.     if (conf)
    30.         rk->rk_conf = *conf;
    31.     else
    32.         rk->rk_conf = rd_kafka_defaultconf;
    33.     rk->rk_refcnt = 2; /* One for caller, one for us. */
    34.     if (rk->rk_type == RD_KAFKA_CONSUMER)
    35.         rk->rk_refcnt++; /* Add another refcount for recv thread */
    36.     /* 设置kafka初始状态为DOWN */
    37.     rd_kafka_set_state(rk, RD_KAFKA_STATE_DOWN);
    38.     pthread_mutex_init(&rk->rk_lock, NULL);
    39.     /* 初始化生产者消息队列 */
    40.     rd_kafka_q_init(&rk->rk_op);
    41.     /* 初始化消费者和错误消息队列 */
    42.     rd_kafka_q_init(&rk->rk_rep);
    43.     /* 花开两朵,各表一枝,这里讨论生产者 */
    44.     switch (rk->rk_type)
    45.     {
    46.     case RD_KAFKA_CONSUMER:
    47.         /* Set up consumer specifics. */
    48.         rk->rk_consumer.offset_file_fd = -1;
    49.         assert(rk->rk_conf.consumer.topic);
    50.         rk->rk_consumer.topic =    strdup(rk->rk_conf.consumer.topic);
    51.         rk->rk_consumer.partition = rk->rk_conf.consumer.partition;
    52.         rk->rk_consumer.offset = rk->rk_conf.consumer.offset;
    53.         rk->rk_consumer.app_offset = rk->rk_conf.consumer.offset;
    54.         /* File-based load&store of offset. */
    55.         if (rk->rk_conf.consumer.offset_file) {
    56.             char buf[32];
    57.             int r;
    58.             char *tmp;
    59.             mode_t mode;
    60.             /* If path is a directory we need to generate the
    61.              * filename (which is a good idea). */
    62.             mode = rd_file_mode(rk->rk_conf.consumer.offset_file);
    63.             if (mode == 0) {
    64.                 /* Error: bail out. */
    65.                 int errno_save = errno;
    66.                 rd_kafka_destroy0(rk);
    67.                 errno = errno_save;
    68.                 return NULL;
    69.             }
    70.             if (S_ISDIR(mode))
    71.                 rk->rk_conf.consumer.offset_file =
    72.                     rd_tsprintf("%s/%s-%"PRIu32,
    73.                          rk->rk_conf.consumer.
    74.                          offset_file,
    75.                          rk->rk_consumer.topic,
    76.                          rk->rk_consumer.partition);
    77.             rk->rk_conf.consumer.offset_file =
    78.                 strdup(rk->rk_conf.consumer.offset_file);
    79.             /* Open file, or create it. */
    80.             if ((rk->rk_consumer.offset_file_fd =
    81.              open(rk->rk_conf.consumer.offset_file,
    82.                  O_CREAT|O_RDWR |
    83.                  (rk->rk_conf.consumer.offset_file_flags &
    84.                  RD_KAFKA_OFFSET_FILE_FLAGMASK),
    85.                  0640)) == -1) {
    86.                 rd_kafka_destroy0(rk);
    87.                 return NULL;
    88.             }
    89.             /* Read current offset from file, or default to 0. */
    90.             r = read(rk->rk_consumer.offset_file_fd,
    91.                  buf, sizeof(buf)-1);
    92.             if (r == -1) {
    93.                 rd_kafka_destroy0(rk);
    94.                 return NULL;
    95.             }
    96.             buf[r] = '';
    97.             rk->rk_consumer.offset = strtoull(buf, &tmp, 10);
    98.             if (tmp == buf) /* empty or not an integer */
    99.                 rk->rk_consumer.offset = 0;
    100.             else
    101.                 rd_kafka_dbg(rk, "OFFREAD",
    102.                      "Read offset %"PRIu64" from "
    103.                      "file %s",
    104.                      rk->rk_consumer.offset,
    105.                      rk->rk_conf.consumer.offset_file);
    106.         }
    107.         break;
    108.     case RD_KAFKA_PRODUCER:
    109.         break;
    110.     }
    111.     /* Construct a conveniant name for this handle. */
    112.     /* 为broker命名 */
    113.     snprintf(rk->rk_broker.name, sizeof(rk->rk_broker.name), "%s#%s-%i",
    114.          broker, rd_kafka_type2str(rk->rk_type), rkid++);
    115.     /* Start the Kafka thread. */
    116.     /* 最关键部分,为生产者/消费者启动了单独的线程,线程函数是rd_kafka_thread_main */
    117.     if ((err = pthread_create(&rk->rk_thread, NULL,
    118.                  rd_kafka_thread_main, rk))) {
    119.         rd_sockaddr_list_destroy(rk->rk_broker.rsal);
    120.         free(rk);
    121.         return NULL;
    122.     }
    123.     return rk;
    124. }
    接下来看看rd_kafka_thread_main里面做了什么?

    点击(此处)折叠或打开

    1. static void *rd_kafka_thread_main (void *arg) {
    2.     rd_kafka_t *rk = arg;
    3.     /* 根据kafka接口状态的不同选择不同的动作 */
    4.     while (!rk->rk_terminate) {
    5.         switch (rk->rk_state)
    6.         {
    7.         case RD_KAFKA_STATE_DOWN:
    8.             /* ..connect() will block until done (or failure) */
    9.             if (rd_kafka_connect(rk) == -1)
    10.                 sleep(1); /*Sleep between connection attempts*/
    11.             break;
    12.         case RD_KAFKA_STATE_CONNECTING:
    13.             break;
    14.         case RD_KAFKA_STATE_UP:
    15.             /* .._wait_*() blocks for as long as the
    16.              * state remains UP. */
    17.             if (rk->rk_type == RD_KAFKA_PRODUCER)
    18.                 rd_kafka_wait_op(rk);/* 对于生产者,需要执行rd_kafka_wait_op操作 */
    19.             else
    20.                 rd_kafka_consumer_wait_io(rk);
    21.             break;
    22.         }
    23.     }
    24.     rd_kafka_destroy(rk);
    25.     return NULL;
    26. }
    继续追踪rd_kafka_wait_op函数。

    点击(此处)折叠或打开

    1. /**
    2.  * Producer: Wait for PRODUCE events from application.
    3.  *
    4.  * Locality: Kafka thread
    5.  */
    6. static void rd_kafka_wait_op (rd_kafka_t *rk) {
    7.     
    8.     /* 循环取消息,发送消息,销毁消息 */
    9.     while (!rk->rk_terminate && rk->rk_state == RD_KAFKA_STATE_UP) {
    10.         rd_kafka_op_t *rko =
    11.             rd_kafka_q_pop(&rk->rk_op, RD_POLL_INFINITE);
    12.         
    13.             rd_kafka_produce_send(rk, rko);
    14.            rd_kafka_op_destroy(rk, rko);
    15.     }
    16. }
    至此,一个异步的消息处理接口已经活生生的展现在大家的面前:

    应用程序中的线程负责向队列扔消息,接口启动的线程负责循环从队列里取消息并向kafka broker发送消息。

    既然有了多线程对队列的操作,异步操作,自然就会有了锁、条件变量的身影。

    创建工作完成了,接下来就是应用生产消息了,这里没有特别之处,就是向队列里扔数据。

    点击(此处)折叠或打开

    1. /**
    2.  * Produce one single message and send it off to the broker.
    3.  *
    4.  * See rdkafka.h for 'msgflags'.
    5.  *
    6.  * Locality: application thread
    7.  */
    8. void rd_kafka_produce (rd_kafka_t *rk, char *topic, uint32_t partition,
    9.          int msgflags,
    10.          char *payload, size_t len) {
    11.     rd_kafka_op_t *rko;
    12.     /* 分配空间 */
    13.     rko = calloc(1, sizeof(*rko));
    14.     rko->rko_type = RD_KAFKA_OP_PRODUCE;
    15.     rko->rko_topic = topic;
    16.     rko->rko_partition = partition;
    17.     rko->rko_flags |= msgflags;
    18.     rko->rko_payload = payload;
    19.     rko->rko_len = len;
    20.     /* 消息入队 */
    21.     rd_kafka_q_enq(&rk->rk_op, rko);
    22. }
    入队操作也是常规操作:

    点击(此处)折叠或打开

    1. /**
    2.  * Enqueue the 'rko' op at the tail of the queue 'rkq'.
    3.  *
    4.  * Locality: any thread.
    5.  */
    6. static inline void rd_kafka_q_enq (rd_kafka_q_t *rkq, rd_kafka_op_t *rko) {
    7.     /* 上面讲到过,入队和出队是不同的线程来完成的,因此需要加锁 */
    8.     pthread_mutex_lock(&rkq->rkq_lock);
    9.     TAILQ_INSERT_TAIL(&rkq->rkq_q, rko, rko_link);
    10.     (void)rd_atomic_add(&rkq->rkq_qlen, 1);
    11.     /* 有数据入队了,通知等待线程 */
    12.     pthread_cond_signal(&rkq->rkq_cond);
    13.     pthread_mutex_unlock(&rkq->rkq_lock);
    14. }

    三、消息处理的细节
    3.1 消息存储
    尾队列存储

    3.2 消息操作接口
    入队:

    点击(此处)折叠或打开

    1. /**
    2.  * Enqueue the 'rko' op at the tail of the queue 'rkq'.
    3.  *
    4.  * Locality: any thread.
    5.  */
    6. static inline void rd_kafka_q_enq (rd_kafka_q_t *rkq, rd_kafka_op_t *rko) {
    7.     pthread_mutex_lock(&rkq->rkq_lock);
    8.     TAILQ_INSERT_TAIL(&rkq->rkq_q, rko, rko_link);
    9.     (void)rd_atomic_add(&rkq->rkq_qlen, 1);
    10.     pthread_cond_signal(&rkq->rkq_cond);
    11.     pthread_mutex_unlock(&rkq->rkq_lock);
    12. }
    出队:

    点击(此处)折叠或打开

    1. /**
    2.  * Pop an op from a queue.
    3.  *
    4.  * Locality: any thread.
    5.  */
    6. static rd_kafka_op_t *rd_kafka_q_pop (rd_kafka_q_t *rkq, int timeout_ms) {
    7.     rd_kafka_op_t *rko;
    8.     rd_ts_t last;
    9.     pthread_mutex_lock(&rkq->rkq_lock);
    10.     
    11.     while (!(rko = TAILQ_FIRST(&rkq->rkq_q)) &&
    12.      (timeout_ms == RD_POLL_INFINITE || timeout_ms > 0)) {
    13.         if (timeout_ms != RD_POLL_INFINITE) {
    14.             last = rd_clock();
    15.             if (pthread_cond_timedwait_ms(&rkq->rkq_cond,
    16.                          &rkq->rkq_lock,
    17.                          timeout_ms) ==
    18.              ETIMEDOUT) {
    19.                 pthread_mutex_unlock(&rkq->rkq_lock);
    20.                 return NULL;
    21.             }
    22.             timeout_ms -= (rd_clock() - last) / 1000;
    23.         } else
    24.             pthread_cond_wait(&rkq->rkq_cond, &rkq->rkq_lock);
    25.     }
    26.     if (rko) {
    27.         TAILQ_REMOVE(&rkq->rkq_q, rko, rko_link);
    28.         (void)rd_atomic_sub(&rkq->rkq_qlen, 1);
    29.     }
    30.     pthread_mutex_unlock(&rkq->rkq_lock);
    31.     return rko;
    32. }
    3.3 入队、出队同步
    pthread_mutex_lock/unlock
    pthread_cond_wait

    3.4 消息发送
    rd_kafka_produce_send->rd_kafka_send_request->rd_kafka_send
    在rd_kafka_send中最终调用了sendmsg系统调用

    点击(此处)折叠或打开

    1. static int rd_kafka_send (rd_kafka_t *rk, const struct msghdr *msg) {
    2.     int r;
    3.     r = sendmsg(rk->rk_broker.s, msg, 0);
    4.     if (r == -1) {
    5.         rd_kafka_fail(rk, "Send failed: %s", strerror(errno));
    6.         return -1;
    7.     }
    8.     rk->rk_broker.stats.tx_bytes += r;
    9.     rk->rk_broker.stats.tx++;
    10.     return r;
    11. }

    四、生产者接口存在的问题与解决办法
    从上面的分析可以看出,librdkafka c接口接收消息和处理消息是异步的,换句话说,生产者接口存在
    问题1: 应用程序调用生产者接口时,是无法马上得知消息是否发送成功的。
    另外,如果kafka broker出现了不可用等情况,kafka c接口依然会不断接收数据,存入队列,但队列没有消费者,导致OOM的风险
    问题2:当kafka broker不可用时,kafka c接口的消息队列不断扩大,内存消耗一直增长。

    受限于kafka当前的通信协议,应用程序暂时无法得知消息是否发送成功的,但我们还是有办法来解决这个问题(不优雅的解决办法)
    生产者接口最终都是通过调用rd_kafka_send来发送消息的。
    点击(此处)折叠或打开
    1. static int rd_kafka_send (rd_kafka_t *rk, const struct msghdr *msg) {
    2.     int r;
    3.     r = sendmsg(rk->rk_broker.s, msg, 0);
    4.     if (r == -1) {
    5.         rd_kafka_fail(rk, "Send failed: %s", strerror(errno));
    6.         return -1;
    7.     }
    8.     rk->rk_broker.stats.tx_bytes += r;
    9.     rk->rk_broker.stats.tx++;
    10.     return r;
    11. }
    当发送消息失败时,会调用rd_kafka_fail函数来处理发送失败的情况。

    点击(此处)折叠或打开

    1. /**
    2.  * Failure propagation to application.
    3.  * Will tear down connection to broker and trigger a reconnect.
    4.  *
    5.  * If 'fmt' is NULL nothing will be logged or propagated to the application.
    6.  * 
    7.  * Locality: Kafka thread
    8.  */
    9. static void rd_kafka_fail (rd_kafka_t *rk, const char *fmt, ...) {
    10.     va_list ap;
    11.     pthread_mutex_lock(&rk->rk_lock);
    12.     rk->rk_err.err = errno;
    13.     rd_kafka_set_state(rk, RD_KAFKA_STATE_DOWN);
    14.     if (rk->rk_broker.s != -1) {
    15.         close(rk->rk_broker.s);
    16.         rk->rk_broker.s = -1;
    17.     }
    18.     if (fmt) {
    19.         va_start(ap, fmt);
    20.         vsnprintf(rk->rk_err.msg, sizeof(rk->rk_err.msg), fmt, ap);
    21.         va_end(ap);
    22.         rd_kafka_log(rk, LOG_ERR, "FAIL", "%s", rk->rk_err.msg);
    23.         /* Send ERR op back to application for processing. */
    24.         rd_kafka_op_reply(rk, RD_KAFKA_OP_ERR,
    25.                  RD_KAFKA_RESP_ERR__FAIL, 0,
    26.                  strdup(rk->rk_err.msg),
    27.                  strlen(rk->rk_err.msg), 0);
    28.     }
    29.     pthread_mutex_unlock(&rk->rk_lock);
    30. }
    从这里可以看到,当发送消息失败时,接口会通过rd_kafka_set_state函数设置kafka为RD_KAFKA_STATE_DOWN状态。
    在应用程序中通过判断rk的状态可以得知kafka的当前状态。c接口封装好了访问当前状态的宏。

    点击(此处)折叠或打开

    1. #define rd_kafka_state(rk) ((rk)->rk_state)
    当应用程序检测到kafka状态为DOWN时,就不再通过c接口向kafka发送消息,这样就可以保证不出现OOM的问题。
    这样是否解决了所有问题了呢?
    答案是否定的。在实施过程中,笔者发现如果kafka broker不可用时,c接口在第一次获知这个不幸之前(通过sendmsg失败?),依然会向broker发送数据,而向一个已经关闭的socket发送数据,会收到对方的rst信号,这将在应用程序中产生SIGPIPE信号,如果不处理,应用程序也会异常退出!!!
    比较简单粗暴的处理办法是:

    点击(此处)折叠或打开

    1. sigemptyset(&set);
    2.  sigaddset(&set, SIGPIPE);
    3.  sigprocmask(SIG_BLOCK, &set, NULL);
  • 相关阅读:
    Thomas Hobbes: Leviathan
    10 Easy Steps to a Complete Understanding of SQL
    day3心得
    py编码终极版
    day2 作业
    Python 中的比较:is 与 ==
    day2-心得
    day1--心得
    day1作业
    python--open用法
  • 原文地址:https://www.cnblogs.com/the-tops/p/6426057.html
Copyright © 2020-2023  润新知