RdKafka高级用法及例子

初识 RdKafka

RdKafka 是一个高性能、跨平台、开源的消息队列库,由 C++ 实现。它既可以作为生产者,也可以作为消费者,支持多种可扩展的消息路由策略,并提供了丰富的配置选项和 API 接口。

在实际应用场景中,RdKafka 可以用来解决大规模数据处理、日志收集、事件推送等问题。相比于其他消息队列库,例如 Apache Kafka、RabbitMQ 等,RdKafka 的最大优势在于它的高吞吐量和低延迟。

高级用法

1. 消费者组管理

RdKafka 支持通过消费者组的方式共享消息接收负载。消费者组是多个消费者实例共同处理相同的一批消息的逻辑概念,每个消费者实例只负责处理一部分消息。这种方式可以提高消息处理的并发度和容错性。

使用 RdKafka 创建消费者组十分简单,只需要在配置项中设置 group.id 参数即可:

conf->set("group.id", "consumer-group");

注意,如果多个消费者实例的 group.id 参数相同,即它们属于同一个消费者组。

2. 消息回溯

在某些情况下,消费者需要重新读取历史消息。RdKafka 提供了两种方式来支持消费者进行消息回溯:

  • 从指定偏移量开始消费。 消费者可以通过设置 offset 参数来指定从哪个偏移量开始消费。例如,以下代码将创建一个新的消费者,从主题 test 的第 10 条消息开始消费:

    RdKafka::TopicPartition *tp = RdKafka::TopicPartition::create("test", 0, 10);
    consumer->assign({tp});
    
  • 从最早或最新的消息开始消费。 消费者可以通过设置 auto.offset.reset 参数来指定从哪里开始消费,可以选择 earliestlatest,分别表示从最早或最新的消息开始消费。例如,以下代码将创建一个新的消费者,从主题 test 的最新消息开始消费:

    conf->set("auto.offset.reset", "latest");
    

3. 消息分区

在 Kafka 中,一个主题可以被分成多个分区,每个分区存储的消息是有序的。RdKafka 支持根据业务需求自定义消息分区策略。

具体来说,RdKafka 提供了一个抽象类 RdKafka::PartitionerCb,通过继承该类并实现 partitioner_cb 方法来自定义分区策略。例如,以下代码将创建一个新的生产者,并使用 CustomPartitioner 自定义分区策略:

class CustomPartitioner : public RdKafka::PartitionerCb {
public:
    int32_t partitioner_cb(const RdKafka::Topic *topic, const std::string *key,
                           int32_t partition_count, void *msg_opaque) override {
        // 自定义分区策略
        return 0;
    }
};

conf->set("partitioner_cb", new CustomPartitioner(), errstr);
producer = RdKafka::Producer::create(conf, errstr);

4. 消息筛选

在某些情况下,消费者只对感兴趣的消息进行处理,而忽略其他消息。RdKafka 提供了两种机制来支持消息筛选:

  • 主题过滤器。 消费者可以设置主题过滤器参数,只接收符合规则的消息。例如,以下代码将创建一个新的消费者,只接收主题名以 test- 开头的消息:

    conf->set("topic.whitelist", "test-*");
    consumer = RdKafka::Consumer::create(conf, errstr);
    
  • 消息过滤器。 消费者可以实现 ConsumeCb 接口,并在 consume_cb 方法中对消息进行筛选。例如,以下代码将创建一个新的消费者,通过 CustomConsumeCb 对消息进行筛选:

    class CustomConsumeCb : public RdKafka::ConsumeCb {
    public:
        void consume_cb (RdKafka::Message &msg, void *opaque) override {
            // 筛选消息
        }
    };
    
    consumer->set_consume_callback(&CustomConsumeCb());
    

示例代码

下面是一个完整的 RdKafka 生产者和消费者实现,支持自定义分区和消息回溯。生产者从标准输入中读取用户输入的消息,然后发送给 Kafka 集群;消费者从指定偏移量开始消费指定主题的消息,并打印到标准输出中。

生产者代码

#include <iostream>
#include <sstream>
#include <string>
#include <signal.h>
#include <librdkafka/rdkafkacpp.h>

const std::string BROKER_LIST = "localhost:9092";
const std::string TOPIC_NAME = "test";
const int PARTITION = RdKafka::Topic::PARTITION_UA;

volatile sig_atomic_t stop = 0;

void sigterm (int sig) {
    stop = 1;
}

class MsgDeliveryRep : public RdKafka::DeliveryReportCb {
public:
    void dr_cb(RdKafka::Message &message) override {
        if (message.err()) {
            std::cerr << "Failed to deliver message: " << message.errstr() << std::endl;
        } else {
            std::cout << "Message delivered: " << message.key() << std::endl;
        }
    }
};

int main() {
    RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL);
    RdKafka::Conf *tconf = RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC);

    std::string errstr;
    conf->set("bootstrap.servers", BROKER_LIST, errstr);
    tconf->set("request.required.acks", "all", errstr);
    tconf->set("partitioner_cb", static_cast<RdKafka::PartitionerCb*>(nullptr), errstr);

    RdKafka::Producer *producer = RdKafka::Producer::create(conf, errstr);
    if (!producer) {
        std::cerr << "Failed to create producer: " << errstr << std::endl;
        exit(1);
    }

    signal(SIGTERM, sigterm);

    MsgDeliveryRep delivery_report;
    producer->set_delivery_report_callback(&delivery_report);

    while (!stop) {
        std::string line;
        if (std::getline(std::cin, line)) {
            std::stringstream ss(line);

            std::string key, value;
            std::getline(ss, key, ',');
            std::getline(ss, value);

            RdKafka::ErrorCode result = producer->produce(
                    RdKafka::Topic::create(producer, TOPIC_NAME, tconf),
                    PARTITION,
                    RdKafka::Producer::RK_MSG_COPY,
                    const_cast<char *>(value.c_str()),
                    value.size(),
                    const_cast<char *>(key.c_str()),
                    key.size(),
                    static_cast<void *>(nullptr)
            );

            if (result != RdKafka::ERR_NO_ERROR) {
                std::cerr << "Failed to produce message: " << RdKafka::err2str(result) << std::endl;
            }
        }
    }

    delete producer;
    delete conf;
    delete tconf;

    return 0;
}

消费者代码

#include <iostream>
#include <string>
#include <signal.h>
#include <librdkafka/rdkafkacpp.h>

const std::string BROKER_LIST = "localhost:9092";
const std::string TOPIC_NAME = "test";
const int PARTITION = RdKafka::Topic::PARTITION_UA;
const int32_t OFFSET = RdKafka::Topic::OFFSET_BEGINNING;

volatile sig_atomic_t stop = 0;

void sigterm (int sig) {
    stop = 1;
}

class MsgRebalanceCb : public RdKafka::RebalanceCb {
public:
    void rebalance_cb (RdKafka::KafkaConsumer *consumer, RdKafka::ErrorCode err,
                       std::vector<RdKafka::TopicPartition*> &partitions) override {
        if (err == RdKafka::ERR__ASSIGN_PARTITIONS) {
            consumer->assign(partitions);
        } else {
            consumer->unassign();
        }
    }
};

void msg_consume(RdKafka::Message* message, void* opaque) {
    switch (message->err()) {
        case RdKafka::ERR__TIMED_OUT:
            break;

        case RdKafka::ERR_NO_ERROR:
            std::cout << "Message received: " << std::string(static_cast<const char*>(message->payload()), message->len()) << std::endl;
            break;

        case RdKafka::ERR__PARTITION_EOF:
            std::cerr << "Reached end of partition." << std::endl;
            break;

        case RdKafka::ERR__UNKNOWN_TOPIC:
        case RdKafka::ERR__UNKNOWN_PARTITION:
            std::cerr << "Consume error: " << message->errstr() << std::endl;
            stop = 1;
            break;

        default:
            std::cerr << "Consume error: " << message->errstr() << std::endl;
            stop = 1;
    }
}

int main() {
    RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL);
    RdKafka::Conf *tconf = RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC);

    std::string errstr;
    conf->set("bootstrap.servers", BROKER_LIST, errstr);
    tconf->set("auto.offset.reset", "smallest", errstr);

    signal(SIGTERM, sigterm);

    RdKafka::KafkaConsumer *consumer = RdKafka::KafkaConsumer::create(conf, errstr);
    if (!consumer) {
        std::cerr << "Failed to create consumer: " << errstr << std::endl;
        exit(1);
    }

    MsgRebalanceCb rebalance_cb;
    consumer->set_rebalance_callback(&rebalance_cb);

    consumer->subscribe({TOPIC_NAME});

    while (!stop) {
        RdKafka::Message *message = consumer->consume(1000);
        msg_consume(message, nullptr);
        delete message;
    }

    consumer->close();
    delete consumer;
    delete conf;
    delete tconf;

    return 0;
}

总结

本文介绍了 RdKafka 的高级用法,包括消费者组管理、消息回溯、消息分区和消息筛选,并给出了一个完整的生产者和消费者实现。希望能够对大家学习和使用 RdKafka 带来一些帮助。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
rdkafka是一个高性能的、纯C++实现的Kafka客户端库。下面是rdkafka库的使用方法: 1. 安装rdkafka 可以通过源码安装或通过包管理器来安装rdkafka。如果是在Linux系统中,可以使用以下命令来安装: ``` sudo apt-get install librdkafka-dev ``` 2. 创建Kafka生产者 ```c++ #include <iostream> #include <string> #include <cstdio> #include <csignal> #include <cstdlib> #include <librdkafka/rdkafkacpp.h> using namespace std; class ExampleDeliveryReportCb : public RdKafka::DeliveryReportCb { public: void dr_cb(RdKafka::Message &message) { if (message.err()) { cerr << "Message delivery failed: " << message.errstr() << endl; } else { cout << "Message delivered to topic " << message.topic_name() << " [" << message.partition() << "] at offset " << message.offset() << endl; } } }; int main() { string brokers = "localhost:9092"; string topic_str = "test"; string errstr; RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL); conf->set("metadata.broker.list", brokers, errstr); ExampleDeliveryReportCb ex_dr_cb; conf->set("dr_cb", &ex_dr_cb, errstr); RdKafka::Producer *producer = RdKafka::Producer::create(conf, errstr); if (!producer) { cerr << "Failed to create producer: " << errstr << endl; exit(1); } RdKafka::Topic *topic = RdKafka::Topic::create(producer, topic_str, NULL, errstr); if (!topic) { cerr << "Failed to create topic: " << errstr << endl; exit(1); } string line; while (getline(cin, line)) { RdKafka::ErrorCode resp = producer->produce(topic, RdKafka::Topic::PARTITION_UA, RdKafka::Producer::RK_MSG_COPY /* Copy payload */, const_cast<char *>(line.c_str()), line.size(), NULL, NULL); if (resp != RdKafka::ERR_NO_ERROR) { cerr << "Failed to produce message: " << RdKafka::err2str(resp) << endl; } else { cout << "Produced message (" << line.size() << " bytes)" << endl; } producer->poll(0); } delete topic; delete producer; RdKafka::wait_destroyed(5000); return 0; } ``` 3. 创建Kafka消费者 ```c++ #include <iostream> #include <string> #include <cstdio> #include <csignal> #include <cstdlib> #include <librdkafka/rdkafkacpp.h> using namespace std; static bool run = true; static void sigterm(int sig) { run = false; } class ExampleConsumeCb : public RdKafka::ConsumeCb { public: void consume_cb(RdKafka::Message &message, void *opaque) { switch (message.err()) { case RdKafka::ERR__TIMED_OUT: break; case RdKafka::ERR_NO_ERROR: cout << "Message consumed: " << string((char *)message.payload()) << endl; break; default: cerr << "Error while consuming message: " << message.errstr() << endl; break; } } }; int main() { string brokers = "localhost:9092"; string group_id = "test-group"; string topic_str = "test"; string errstr; RdKafka::Conf *conf = RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL); conf->set("metadata.broker.list", brokers, errstr); conf->set("group.id", group_id, errstr); ExampleConsumeCb ex_consume_cb; conf->set("consume_cb", &ex_consume_cb, errstr); RdKafka::Consumer *consumer = RdKafka::Consumer::create(conf, errstr); if (!consumer) { cerr << "Failed to create consumer: " << errstr << endl; exit(1); } RdKafka::Topic *topic = RdKafka::Topic::create(consumer, topic_str, NULL, errstr); if (!topic) { cerr << "Failed to create topic: " << errstr << endl; exit(1); } RdKafka::ErrorCode resp = consumer->start(topic, 0, RdKafka::Topic::OFFSET_BEGINNING); if (resp != RdKafka::ERR_NO_ERROR) { cerr << "Failed to start consumer: " << RdKafka::err2str(resp) << endl; exit(1); } signal(SIGINT, sigterm); while (run) { RdKafka::Message *message = consumer->consume(1000); ex_consume_cb.consume_cb(*message, NULL); delete message; } consumer->stop(topic, 0); consumer->wait_destroyed(5000); delete topic; delete consumer; RdKafka::wait_destroyed(5000); return 0; } ``` 以上是创建Kafka生产者和消费者的代码示例,需要在程序中修改Kafka Broker的地址和端口,以及topic的名称和其他配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值