一、操作Kafka
1.1、安装librdkafka
git地址:https://github.com/confluentinc/librdkafka
shell
git clone git@github.com:confluentinc/librdkafka.git
cd librdkafka
./configure

然后经典的编译安装
shell
make
make install
ldconfig
注意: :安装后头文件位于 /usr/local/include/librdkafka/,库文件位于/usr/local/lib/
二、消费者程序分析
在librdkafka/examples/下存在一些示例文件,先看下如何进行操作使用:

2.1、运行可执行程序

提示需要填入相关参数
shell
# 先本地运行,端口默认为9092
./consumer localhost:9092 my-consumer-group-1 consumer test
- broker:服务地址
- group.id: 消费组编号
- group.protocol: 消费组使用的 再平衡协议 ,是一个枚举值,
classic和consumer - topic: 主题
**注意:**执行程序时,先确保zookeeper和kafka服务已经启动
2.2、程序结构
参数解析
c
// consumer.c
/*
* Argument validation
*/
if (argc < 4) {
fprintf(stderr,
"%% Usage: "
"%s <broker> <group.id> <group.protocol> <topic1> "
"<topic2>..\n",
argv[0]);
return 1;
}
// 参数格式:<broker> <group.id> <group.protocol> <topic1> <topic2>..
brokers = argv[1]; // Kafka broker地址
groupid = argv[2]; // 消费者组ID
group_protocol = argv[3]; // 再平衡协议(classic/consumer)
topics = &argv[4]; // 要订阅的主题列表
topic_cnt = argc - 4; // 主题数量
关键配置项
c
// 1. 设置bootstrap servers
rd_kafka_conf_set(conf, "bootstrap.servers", brokers, ...);
// 2. 设置消费者组ID
rd_kafka_conf_set(conf, "group.id", groupid, ...);
// 3. 设置再平衡协议
rd_kafka_conf_set(conf, "group.protocol", group_protocol, ...);
// 4. 设置偏移量重置策略(earliest/latest)
rd_kafka_conf_set(conf, "auto.offset.reset", "earliest", ...);
消息处理循环
c
while (run) {
// 轮询消息,超时时间100ms
rd_kafka_message_t *rkm = rd_kafka_consumer_poll(rk, 100);
if (!rkm) continue; // 超时,继续轮询
if (rkm->err) {
// 处理消费者错误
fprintf(stderr, "%% Consumer error: %s\n",
rd_kafka_message_errstr(rkm));
} else {
// 成功接收到消息
printf("Message on %s [%d] at offset %lld\n",
rd_kafka_topic_name(rkm->rkt),
rkm->partition,
rkm->offset);
if (rkm->key) printf(" Key: %.*s\n", (int)rkm->key_len, (char*)rkm->key);
if (rkm->payload) printf(" Value: %.*s\n", (int)rkm->len, (char*)rkm->payload);
}
rd_kafka_message_destroy(rkm); // 释放消息资源
}
三、生产者程序分析
在examples/目录下,存在两个生产者程序,一个是纯C,一个是CPP的,先看下C的
3.1、C程序特点
- 采用异步发送机制
- 使用
rd_kafka_producev()异步发送消息 - 消息发送到内部队列后立即返回
- 通过回调函数获取发送结果
- 使用
c
retry:
err = rd_kafka_producev(
/* Producer handle */
rk,
/* Topic name */
RD_KAFKA_V_TOPIC(topic),
/* Make a copy of the payload. */
RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
/* Message value and length */
RD_KAFKA_V_VALUE(buf, len),
/* Per-Message opaque, provided in
* delivery report callback as
* msg_opaque. */
RD_KAFKA_V_OPAQUE(NULL),
/* End sentinel */
RD_KAFKA_V_END);
if (err) {
/*
* Failed to *enqueue* message for producing.
*/
fprintf(stderr,
"%% Failed to produce to topic %s: %s\n", topic,
rd_kafka_err2str(err));
if (err == RD_KAFKA_RESP_ERR__QUEUE_FULL) {
/* If the internal queue is full, wait for
* messages to be delivered and then retry.
* The internal queue represents both
* messages to be sent and messages that have
* been sent or failed, awaiting their
* delivery report callback to be called.
*
* The internal queue is limited by the
* configuration property
* queue.buffering.max.messages and
* queue.buffering.max.kbytes */
rd_kafka_poll(rk,
1000 /*block for max 1000ms*/);
goto retry;
}
} else {
fprintf(stderr,
"%% Enqueued message (%zd bytes) "
"for topic %s\n",
len, topic);
}
- 生产者数据流
text
用户调用 producev()
↓
检查参数有效性
↓
尝试放入内部队列 ←─ 可能失败(队列满)
↓
成功入队 → 立即返回成功
↓
后台发送线程轮询队列
↓
批量发送到 Kafka Broker
↓
收到 Broker 响应
↓
调用 dr_msg_cb() 通知结果
- 消息状态机
text
// 消息生命周期
MESSAGE_CREATED // 消息创建
↓
ENQUEUED_SUCCESSFULLY // 成功入队(producev() 返回成功)
↓
SENDING // 后台线程正在发送
↓
SENT_SUCCESSFULLY // 发送成功(dr_msg_cb 通知)
OR
SENT_FAILED // 发送失败(dr_msg_cb 通知)
3.2、C++程序特点
大体思路和C程序一致,只不过进行了类封装
c++
class ExampleDeliveryReportCb : public RdKafka::DeliveryReportCb {
public:
void dr_cb(RdKafka::Message &message) {
/* If message.err() is non-zero the message delivery failed permanently
* for the message. */
if (message.err())
std::cerr << "% Message delivery failed: " << message.errstr()
<< std::endl;
else
std::cerr << "% Message delivered to topic " << message.topic_name()
<< " [" << message.partition() << "] at offset "
<< message.offset() << std::endl;
}
};
四、总结
4.1、消费者组与再平衡
-
消费者组(Consumer Group):多个消费者实例可以组成一个组,共同消费一个或多个主题
-
再平衡协议(Rebalance Protocol):当消费者加入或离开组时,如何重新分配分区
classicconsumer
4.2、偏移量管理
auto.offset.reset:当没有提交的偏移量时从哪里开始消费earliest:从最早的消息开始latest:从最新的消息开始
- 消费者会自动定期提交偏移量
4.3、生产者确认机制
-
异步发送:提高吞吐量,但需要处理发送结果
-
队列缓冲:生产者内部有消息队列,可配置大小
-
发送确认:通过回调函数或轮询获取发送结果