librdkafka 配置相关源码阅读笔记

本文介绍了librdkafka的配置相关源码分析,包括全局配置和topic配置的结构体定义、配置项设置与读取、回调机制等内容,深入解析了如何通过结构体offset和type字段实现配置项的映射和操作。

概述

  • librdkafka 是kafka官方推荐的C客户端库,提供生产者API、低级和高级消费者API。根据librdkafka提供的文档介绍,其性能也是蛮不错的。近期因项目需要,简单阅读了librdkafka-0.11.1.x版本topic配置和全局配置相关的一些代码,并整理了一些笔记。
  • librdkafka 下载地址: https://github.com/edenhill/librdkafka

全局配置和topic配置

  • 配置项分配全局配置和topic配置两类。

    • 全局配置如socket.timeout.ms,error_cb,log_cb,group.id,rebalance_cb等.
    • topic配置如 request.timeout.ms,auto.commit.enable,auto.commit.interval.ms,auto.offset.reset,offset.store.method
  • 支持配置项介绍

  • 相关API

    • 全局配置项主要通过rd_kafka_conf_set进行设置,例如:

          char* group = "rdkafka_consumer_example";
          if (rd_kafka_conf_set(conf, "group.id", group,  errstr, sizeof(errstr)) !=  RD_KAFKA_CONF_OK)
          {
                  fprintf(stderr, "%% %s\n", errstr);
                  exit(1);
          }
    • 熟悉C的读者可能会有疑问,C没有类似java的反射机制,如何通过一个通用的接口,将字符串描述的配置项映射到结构体的相关字段呢?比如说"group.id"这个字符串,如何知道应该把对应的值赋给rd_kafka_conf_s::group_id_str这个字段呢? 我们后面会看到,这里主要是通过rd_kafka_property结构的offset和type字段来实现的。

    • topic相关配置主要通过rd_kafka_topic_conf_set进行设置,例如:

          /* Kafka topic configuration */
          topic_conf = rd_kafka_topic_conf_new();
          rd_kafka_topic_conf_set(topic_conf, "auto.offset.reset", "earliest", NULL, 0);
    • 对于callback以及其他指针类(比如透传参数opaque)的设置,提供了一组专门的设置接口,如:

          // 全局配置
          rd_kafka_conf_set_dr_msg_cb
          rd_kafka_conf_set_consume_cb
          rd_kafka_conf_set_rebalance_cb
          rd_kafka_conf_set_offset_commit_cb
          rd_kafka_conf_set_error_cb
          rd_kafka_conf_set_throttle_cb
          rd_kafka_conf_set_log_cb
          rd_kafka_conf_set_stats_cb
          rd_kafka_conf_set_socket_cb
          rd_kafka_conf_set_connect_cb
          rd_kafka_conf_set_closesocket_cb
          rd_kafka_conf_set_open_cb
          rd_kafka_conf_set_opaque
      
          // topic 配置
          rd_kafka_topic_conf_set_opaque
          rd_kafka_topic_conf_set_partitioner_cb
    • PS:额外补充一句,librdkafka针对网络异常(如连接失败、通信过程中socket异常),都是通过回调方式通知上层应用的。如果需要处理这些错误,则必须设置相应的回调函数。否则应用程序将无法及时感知到这些网络错误,有可能会出现前面接口一路调用都没有返回错误,但最后生产消息却发送失败的情况。

源码分析

结构体定义

rd_kafka_conf_trd_kafka_topic_conf_t

  • 该结构中的字段基本上与文档中的配置项对应,这里不再详细罗列,有兴趣可直接阅读源码。

rd_kafka_property

  • 这个结构用来记录rd_kafka_conf_trd_kafka_topic_conf_t的默认值,以及提供一种通用的方法来读取和设置上述两个结构体中的各字段值。换言之,就是为了避免针对这两个conf类型的每一个字段开一个set/get的API,或者虽然提供了一个看似通用的接口,但在其内部通过大量的else if语句罗列每个字段。
  • 有一个该类型的全局数组,定义了所有全局配置项和topic配置项(这意味着,尽管针对全局配置和topic配置的入口API不同,但最终调用的内部实现基本都是一样的。只是根据scope字段进行过滤区分)的默认值:
    static const struct rd_kafka_property rd_kafka_properties[]
  • 每个rd_kafka_property结构对应一条配置项.
  • 该结构体定义如下:

    struct rd_kafka_property {
        rd_kafka_conf_scope_t scope;
        const char *name;
        enum {
            _RK_C_STR,
            _RK_C_INT,
            _RK_C_S2I,  /* String to Integer mapping.
                     * Supports limited canonical str->int mappings
                     * using s2i[] */
            _RK_C_S2F,  /* CSV String to Integer flag mapping (OR:ed) */
            _RK_C_BOOL,
            _RK_C_PTR,  /* Only settable through special set functions */
                    _RK_C_PATLIST, /* Pattern list */
                    _RK_C_KSTR, /* Kafka string */
                    _RK_C_ALIAS, /* Alias: points to other property through .sdef */
                    _RK_C_INTERNAL, /* Internal, don't expose to application */
                    _RK_C_INVALID,  /* Invalid property, used to catch known
                                     * but unsupported Java properties. */
        } type;
        int   offset;
        const char *desc;
        int   vmin;
        int   vmax;
        int   vdef;        /* Default value (int) */
        const char *sdef;  /* Default value (string) */
            void  *pdef;       /* Default value (pointer) */
        struct {
            int val;
            const char *str;
        } s2i[16];  /* _RK_C_S2I and _RK_C_S2F */
    
        /* Value validator (STR) */
        int (*validate) (const struct rd_kafka_property *prop,
                 const char *val, int ival);
    
            /* Configuration object constructors and destructor for use when
             * the property value itself is not used, or needs extra care. */
            void (*ctor) (int scope, void *pconf);
            void (*dtor) (int scope, void *pconf);
            void (*copy) (int scope, void *pdst, const void *psrc,
                          void *dstptr, const void *srcptr,
                          size_t filter_cnt, const char **filter);
    
            rd_kafka_conf_res_t (*set) (int scope, void *pconf,
                                        const char *name, const char *value,
                                        void *dstptr,
                                        rd_kafka_conf_set_mode_t set_mode,
                                        char *errstr, size_t errstr_size);
    };
    rd_kafka_property主要字段含义:
    • scope: 表示该rd_kafka_property是全局配置还是topic配置项,以及是生产者属性还是消费者属性。例如_RK_GLOBAL|_RK_PRODUCER.
      typedef enum {
          _RK_GLOBAL = 0x1,
          _RK_PRODUCER = 0x2,
          _RK_CONSUMER = 0x4,
          _RK_TOPIC = 0x8,
              _RK_CGRP = 0x10
      } rd_kafka_conf_scope_t;
  • name : 配置项的名称,例如"queue.buffering.max.messages"
  • type : 配置项的取值类型,例如字符串、整型等。这是一个枚举,其定义如下:

    • _RK_C_STR 字符串类型
    • _RK_C_INT 整型
    • _RK_C_S2I 字符串到整型的映射,这样配置接口传入的是一个字符串,库内部将其转为对应的数字使用。例如:"auto.offset.reset"这个配置:

          { RD_KAFKA_OFFSET_BEGINNING, "smallest" },
          { RD_KAFKA_OFFSET_BEGINNING, "earliest" },
          { RD_KAFKA_OFFSET_BEGINNING, "beginning" },
          { RD_KAFKA_OFFSET_END, "largest" },
    • _RK_C_S2F CSV字符串到整型的映射,配置接口传入的是以逗号间隔的多个字符串,库内部以掩码方式存储。根据字符串执行|等位操作。 例如,

          { RD_KAFKA_DBG_BROKER,   "broker" },
          { RD_KAFKA_DBG_TOPIC,    "topic" },
          { RD_KAFKA_DBG_METADATA, "metadata" },
          { RD_KAFKA_DBG_QUEUE,    "queue" },
          { RD_KAFKA_DBG_MSG,      "msg" },
          { RD_KAFKA_DBG_PROTOCOL, "protocol" },
      
      // 相关定义如下(每个值对应一个比特位):  
          #define RD_KAFKA_DBG_GENERIC        0x1
          #define RD_KAFKA_DBG_BROKER         0x2
          #define RD_KAFKA_DBG_TOPIC          0x4
          #define RD_KAFKA_DBG_METADATA       0x8
          #define RD_KAFKA_DBG_FEATURE        0x10
          #define RD_KAFKA_DBG_QUEUE          0x20
          #define RD_KAFKA_DBG_MSG            0x40
          #define RD_KAFKA_DBG_PROTOCOL       0x80
    • offset : 该字段记录rd_kafka_property所表示的配置项,在rd_kafka_conf_trd_kafka_topic_conf_t结构中对应字段的偏移。当我们需要设置或读取某个配置项的值时,从rd_kafka_conf_trd_kafka_topic_conf_t的起始地址+offset找到其存储的地址,再配合type字段就可以了。

      • offset的初始化是通过宏_RK_RKT完成的,其定义如下:
          #define _RK(field)  offsetof(rd_kafka_conf_t, field)
          #define _RKT(field) offsetof(rd_kafka_topic_conf_t, field)
      • 这样看仍然有一点抽象,我们以_RK(max_msg_size)为例(其中max_msg_size是rd_kafka_conf_t中的一个字段),这个宏展开之后是这个样子:

        ((size_t) &((rd_kafka_conf_t *)0)->max_msg_size)
      • 当需要读取或设置rd_kafka_conf_t/rd_kafka_topic_conf_t中某个字段值时,通过_RK_PTR宏还原对应的偏移和数据类型。这样就实现了一种通用的方法来访问各个字段,而无需针对rd_kafka_conf_t/rd_kafka_topic_conf_t中的每个字段进行罗列。

        
        #define _RK_PTR(TYPE,BASE,OFFSET)  (TYPE)(void *)(((char *)(BASE))+(OFFSET))   
        
        // 使用_RK_PTR宏的代码大概是这样样子:  
        case _RK_C_BOOL:
                val = (*_RK_PTR(int *, conf, prop->offset) ? "true" : "false");  
        // 其中_RK_PTR宏展开是这样样子:  
        (int *)(void *)(((char *)(conf))+(prop->offset))    
    • vdef,sdef,pdef: 分别给出int、string、指针类型的默认值。
    • s2i[16]: 用于 _RK_C_S2I_RK_C_S2F类型的属性,记录字符串名称和数字之间的映射关系。 如前面提到的 { RD_KAFKA_OFFSET_BEGINNING, "smallest" }

          struct {
              int val;
              const char *str;
          } s2i[16];  /* _RK_C_S2I and _RK_C_S2F */
rd_kafka_properties数组的定义
  • 针对全局和topic conf结构中的每个字段,初始化一个rd_kafka_property对象

    /**
     * librdkafka configuration property definitions.
     */
    static const struct rd_kafka_property rd_kafka_properties[] = {
        ……
        // 以{}方式对数组中每个成员进行初始化.这里仅摘取个别字段作为示例.  
        { _RK_GLOBAL, "client.id", _RK_C_STR, _RK(client_id_str),
          "Client identifier.",
          .sdef =  "rdkafka" },
        { _RK_GLOBAL, "message.max.bytes", _RK_C_INT, _RK(max_msg_size),
          "Maximum Kafka protocol request message size.",
          1000, 1000000000, 1000000 },
        ……
        { _RK_TOPIC|_RK_CONSUMER, "auto.offset.reset", _RK_C_S2I,
          _RKT(auto_offset_reset),
          "Action to take when there is no initial offset in offset store "
          "or the desired offset is out of range: "
          "'smallest','earliest' - automatically reset the offset to the smallest offset, "
          "'largest','latest' - automatically reset the offset to the largest offset, "
          "'error' - trigger an error which is retrieved by consuming messages "
          "and checking 'message->err'.",
          .vdef = RD_KAFKA_OFFSET_END,
          .s2i = {
                { RD_KAFKA_OFFSET_BEGINNING, "smallest" },
                { RD_KAFKA_OFFSET_BEGINNING, "earliest" },
                { RD_KAFKA_OFFSET_BEGINNING, "beginning" },
                { RD_KAFKA_OFFSET_END, "largest" },
                { RD_KAFKA_OFFSET_END, "latest" },
                { RD_KAFKA_OFFSET_END, "end" },
                { RD_KAFKA_OFFSET_INVALID, "error" },
            }
        },
    ……
    }

conf结构的创建及初始化

  • rd_kafka_conf_newrd_kafka_topic_conf_new代码都比较简单,申请内存、调用默认的初始化进行赋值。其内部都是调用同一个函数,通过_RK_TOPIC_RK_GLOBAL区分。

    rd_kafka_conf_t *rd_kafka_conf_new (void) {
        rd_kafka_conf_t *conf = rd_calloc(1, sizeof(*conf));
        rd_kafka_defaultconf_set(_RK_GLOBAL, conf);
        return conf;
    }
    
    rd_kafka_topic_conf_t *rd_kafka_topic_conf_new (void) {
        rd_kafka_topic_conf_t *tconf = rd_calloc(1, sizeof(*tconf));
        rd_kafka_defaultconf_set(_RK_TOPIC, tconf);
        return tconf;
    }
  • rd_kafka_defaultconf_set遍历rd_kafka_properties数组,根据是topic设置还是全局设置,过滤掉不需要的数组元素。然后以prop->sdefprop->vdefprop->pdef等默认值为参数,调用rd_kafka_anyconf_set_prop0
  • rd_kafka_anyconf_set_prop0核心流程就是根据rd_kafka_properties 数组中的各个元素,将函数参数中提供的值写到offset指定的位置。 其核心代码如下:

        switch (prop->type)
        {
            case _RK_C_STR:
            {
                // 将conf+(prop->offset)的内存解析为char **类型  
                char **str = _RK_PTR(char **, conf, prop->offset);
                if (*str)
                    rd_free(*str);
                if (istr)
                    *str = rd_strdup(istr);
                else
                    // 新申请一块内存赋给*str,并将prop->sdef(即字符串默认值)拷贝到该内存  
                    *str = prop->sdef ? rd_strdup(prop->sdef) : NULL;
                return RD_KAFKA_CONF_OK;
            }
            ……
            case _RK_C_BOOL:
            case _RK_C_INT:
            case _RK_C_S2I:
            case _RK_C_S2F:
            {
                int *val = _RK_PTR(int *, conf, prop->offset);
    
                // 注意:ival就是prop->vdef
                if (prop->type == _RK_C_S2F) {
                    // _RK_C_S2F类型,可以有多个字符串值并存,转为掩码方式.每个值对应一个比特位.  
                    switch (set_mode)
                    {
                    case _RK_CONF_PROP_SET_REPLACE:
                        *val = ival;
                        break;
                    case _RK_CONF_PROP_SET_ADD:
                        *val |= ival;
                        break;
                    case _RK_CONF_PROP_SET_DEL:
                        *val &= ~ival;
                        break;
                    }
                } else {
                    /* Single assignment */
                    // 把int型的默认值赋给offset指向的内存地址
                    *val = ival;
    
                }
                return RD_KAFKA_CONF_OK;
            }
            ……
            default:
                rd_kafka_assert(NULL, !*"unknown conf type");
        }

设置配置项接口

  • 设置配置项主要通过rd_kafka_conf_setrd_kafka_topic_conf_set这两个函数(此外,针对指针、回调函数还有一些专门的接口).
  • 上述两个函数都会调用rd_kafka_anyconf_set
  • rd_kafka_anyconf_set遍历全局数组rd_kafka_properties,找到和当前设置项名称匹配的rd_kafka_property对象指针
  • 通过上述rd_kafka_property指针调用rd_kafka_anyconf_set_prop,在做了一些合法性检查和转换之后,也是调用rd_kafka_anyconf_set_prop0,这就和conf对象初始化的流程类似了。只不过初始化的时候,是以rd_kafka_properties中的默认值为参数调用rd_kafka_anyconf_set_prop0,而此时是以用户传入的新值为参数。
  • 这里再对rd_kafka_anyconf_set_prop中的参数转换做一些补充说明。例如对于_RK_C_S2I类型,其在conf结构中对应的是一个int类型,而用户传入的是一个字符串类型。需要将字符串转成int:

    • 遍历prop->s2i数组,比较数组中存的字符串和用户传入的字符串。
    • 如二者相等,将prop->s2i[j].val作为新的值传给rd_kafka_anyconf_set_prop0;
    • 否则继续循环或结束(说明用户传入字符串非法)
    // rd_kafka_anyconf_set_prop 针对 _RK_C_S2I 和 _RK_C_S2F 类型的核心代码  
    /* Match string to s2i table entry */
        for (j = 0 ; j < (int)RD_ARRAYSIZE(prop->s2i); j++) {
            int new_val;
    
            if (!prop->s2i[j].str)
                continue;
    
            if (strlen(prop->s2i[j].str) == (size_t)(t-s) &&
                 !rd_strncasecmp(prop->s2i[j].str, s,
                         (int)(t-s)))
                new_val = prop->s2i[j].val;
            else
                continue;
    
            rd_kafka_anyconf_set_prop0(scope, conf, prop,
                                                       value, new_val,
                                                       set_mode,
                                                       errstr, errstr_size);
    
            if (prop->type == _RK_C_S2F) {
                /* Flags: OR it in: do next */
                break;
            } else {
                /* Single assignment */
                return RD_KAFKA_CONF_OK;
            }
        }

读取配置项

  • 查询配置项主要通过rd_kafka_conf_getrd_kafka_topic_conf_get, 二者都会调用rd_kafka_anyconf_get
  • rd_kafka_anyconf_get遍历rd_kafka_properties数组,比较prop->name和用户传入的配置项名称,如果相同,则调用rd_kafka_anyconf_get0
  • rd_kafka_anyconf_get0通过配置项类型和偏移,通过_RK_PTR宏提取对应的字段值。例如:

       switch (prop->type)
        {
        case _RK_C_STR:
                val = *_RK_PTR(const char **, conf, prop->offset);
                break;
        case _RK_C_KSTR:
        {
                const rd_kafkap_str_t **kstr = _RK_PTR(const rd_kafkap_str_t **, conf, prop->offset);
                if (*kstr)
                        val = (*kstr)->str;
                break;
        }
        ……
        case _RK_C_BOOL:
                val = (*_RK_PTR(int *, conf, prop->offset) ? "true" : "false");
                break;
        case _RK_C_INT:
                rd_snprintf(tmp, sizeof(tmp), "%i", *_RK_PTR(int *, conf, prop->offset));
                val = tmp;
                break;
        ……
        }

打印所有配置项

  • rd_kafka_conf_dumprd_kafka_topic_conf_dump,调用rd_kafka_anyconf_dump,后者遍历rd_kafka_properties中的各个元素,调用rd_kafka_anyconf_get0获取其对应的值,并拼装成字符串。其格式如下:

    
    # Global config
    
    builtin.features = gzip,snappy,ssl,sasl,regex,lz4,sasl_gssapi,sasl_plain,sasl_scram,plugins
    client.id = rdkafka
    message.max.bytes = 1000000
    message.copy.max.bytes = 65535
    ……
    
    # Topic config
    
    request.required.acks = 1
    request.timeout.ms = 5000
    message.timeout.ms = 300000

总结

  • 本文简单整理了librdkafka全局配置项和topic配置的内部设置、查询实现方式。由于水平和时间有限,难免有所疏漏,欢迎大家批评指正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值