概述
- 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等
- 全局配置如
支持配置项介绍
- 官方介绍 https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
- 翻译的中文版本网上也有很多,而且基本上和kafka本身的配置是对应的。比如: https://blog.csdn.net/blackocular/article/details/56677325
相关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_cbPS:额外补充一句,librdkafka针对网络异常(如连接失败、通信过程中socket异常),都是通过回调方式通知上层应用的。如果需要处理这些错误,则必须设置相应的回调函数。否则应用程序将无法及时感知到这些网络错误,有可能会出现前面接口一路调用都没有返回错误,但最后生产消息却发送失败的情况。
源码分析
结构体定义
rd_kafka_conf_t 和 rd_kafka_topic_conf_t
- 该结构中的字段基本上与文档中的配置项对应,这里不再详细罗列,有兴趣可直接阅读源码。
rd_kafka_property
- 这个结构用来记录
rd_kafka_conf_t和rd_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_S2FCSV字符串到整型的映射,配置接口传入的是以逗号间隔的多个字符串,库内部以掩码方式存储。根据字符串执行|等位操作。 例如,{ 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 0x80offset: 该字段记录rd_kafka_property所表示的配置项,在rd_kafka_conf_t或rd_kafka_topic_conf_t结构中对应字段的偏移。当我们需要设置或读取某个配置项的值时,从rd_kafka_conf_t或rd_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))
- 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_new和rd_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->sdef或prop->vdef或prop->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_set和rd_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_get和rd_kafka_topic_conf_get, 二者都会调用rd_kafka_anyconf_get rd_kafka_anyconf_get遍历rd_kafka_properties数组,比较prop->name和用户传入的配置项名称,如果相同,则调用rd_kafka_anyconf_get0rd_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_dump和rd_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配置的内部设置、查询实现方式。由于水平和时间有限,难免有所疏漏,欢迎大家批评指正。
本文介绍了librdkafka的配置相关源码分析,包括全局配置和topic配置的结构体定义、配置项设置与读取、回调机制等内容,深入解析了如何通过结构体offset和type字段实现配置项的映射和操作。
420

被折叠的 条评论
为什么被折叠?



