转载:kafka c接口librdkafka介绍之二:生产者接口
转载:from:http://www.verydemo.com/demo_c92_i210679.html
这个程序虽然我调试过,也分析过,但是没有记录笔记,发现下边这篇文章分析直接透彻,拿来借用,聊以自省,开发一直要研究透彻源码,学习其中的处理手段!
kafka c接口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实例
点击(此处)折叠或打开
- rk = rd_kafka_new(RD_KAFKA_PRODUCER, broker, NULL)
step 2)发送数据
点击(此处)折叠或打开
- rd_kafka_produce(rk, topic, partition, RD_KAFKA_OP_F_FREE, opbuf, len)
下面就看看这两步背后librdkafka做了什么?
先看看rd_kafka_new做了什么
点击(此处)折叠或打开
- rd_kafka_t *rd_kafka_new (rd_kafka_type_t type, const char *broker,
- const rd_kafka_conf_t *conf) {
- rd_kafka_t *rk;
- rd_sockaddr_list_t *rsal;
- const char *errstr;
- static int rkid = 0;
- int err;
- /* If broker is NULL, default it to localhost. */
- if (!broker)
- broker = "localhost";
- /* 获取broker的地址信息 */
- if (!(rsal = rd_getaddrinfo(broker, RD_KAFKA_PORT_STR, AI_ADDRCONFIG,
- AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
- &errstr))) {
- rd_kafka_log(NULL, LOG_ERR, "GETADDR",
- "getaddrinfo failed for '%s': %s",
- broker, errstr);
- return NULL;
- }
- /*
- * Set up the handle.
- */
- rk = calloc(1, sizeof(*rk));
- rk->rk_type = type;
- rk->rk_broker.rsal = rsal;
- rk->rk_broker.s = -1;
- if (conf)
- rk->rk_conf = *conf;
- else
- rk->rk_conf = rd_kafka_defaultconf;
- rk->rk_refcnt = 2; /* One for caller, one for us. */
- if (rk->rk_type == RD_KAFKA_CONSUMER)
- rk->rk_refcnt++; /* Add another refcount for recv thread */
- /* 设置kafka初始状态为DOWN */
- rd_kafka_set_state(rk, RD_KAFKA_STATE_DOWN);
- pthread_mutex_init(&rk->rk_lock, NULL);
- /* 初始化生产者消息队列 */
- rd_kafka_q_init(&rk->rk_op);
- /* 初始化消费者和错误消息队列 */
- rd_kafka_q_init(&rk->rk_rep);
- /* 花开两朵,各表一枝,这里讨论生产者 */
- switch (rk->rk_type)
- {
- case RD_KAFKA_CONSUMER:
- /* Set up consumer specifics. */
- rk->rk_consumer.offset_file_fd = -1;
- assert(rk->rk_conf.consumer.topic);
- rk->rk_consumer.topic = strdup(rk->rk_conf.consumer.topic);
- rk->rk_consumer.partition = rk->rk_conf.consumer.partition;
- rk->rk_consumer.offset = rk->rk_conf.consumer.offset;
- rk->rk_consumer.app_offset = rk->rk_conf.consumer.offset;
- /* File-based load&store of offset. */
- if (rk->rk_conf.consumer.offset_file) {
- char buf[32];
- int r;
- char *tmp;
- mode_t mode;
- /* If path is a directory we need to generate the
- * filename (which is a good idea). */
- mode = rd_file_mode(rk->rk_conf.consumer.offset_file);
- if (mode == 0) {
- /* Error: bail out. */
- int errno_save = errno;
- rd_kafka_destroy0(rk);
- errno = errno_save;
- return NULL;
- }
- if (S_ISDIR(mode))
- rk->rk_conf.consumer.offset_file =
- rd_tsprintf("%s/%s-%"PRIu32,
- rk->rk_conf.consumer.
- offset_file,
- rk->rk_consumer.topic,
- rk->rk_consumer.partition);
- rk->rk_conf.consumer.offset_file =
- strdup(rk->rk_conf.consumer.offset_file);
- /* Open file, or create it. */
- if ((rk->rk_consumer.offset_file_fd =
- open(rk->rk_conf.consumer.offset_file,
- O_CREAT|O_RDWR |
- (rk->rk_conf.consumer.offset_file_flags &
- RD_KAFKA_OFFSET_FILE_FLAGMASK),
- 0640)) == -1) {
- rd_kafka_destroy0(rk);
- return NULL;
- }
- /* Read current offset from file, or default to 0. */
- r = read(rk->rk_consumer.offset_file_fd,
- buf, sizeof(buf)-1);
- if (r == -1) {
- rd_kafka_destroy0(rk);
- return NULL;
- }
- buf[r] = '\0';
- rk->rk_consumer.offset = strtoull(buf, &tmp, 10);
- if (tmp == buf) /* empty or not an integer */
- rk->rk_consumer.offset = 0;
- else
- rd_kafka_dbg(rk, "OFFREAD",
- "Read offset %"PRIu64" from "
- "file %s",
- rk->rk_consumer.offset,
- rk->rk_conf.consumer.offset_file);
- }
- break;
- case RD_KAFKA_PRODUCER:
- break;
- }
- /* Construct a conveniant name for this handle. */
- /* 为broker命名 */
- snprintf(rk->rk_broker.name, sizeof(rk->rk_broker.name), "%s#%s-%i",
- broker, rd_kafka_type2str(rk->rk_type), rkid++);
- /* Start the Kafka thread. */
- /* 最关键部分,为生产者/消费者启动了单独的线程,线程函数是rd_kafka_thread_main */
- if ((err = pthread_create(&rk->rk_thread, NULL,
- rd_kafka_thread_main, rk))) {
- rd_sockaddr_list_destroy(rk->rk_broker.rsal);
- free(rk);
- return NULL;
- }
- return rk;
- }
接下来看看rd_kafka_thread_main里面做了什么?
点击(此处)折叠或打开
- static void *rd_kafka_thread_main (void *arg) {
- rd_kafka_t *rk = arg;
- /* 根据kafka接口状态的不同选择不同的动作 */
- while (!rk->rk_terminate) {
- switch (rk->rk_state)
- {
- case RD_KAFKA_STATE_DOWN:
- /* ..connect() will block until done (or failure) */
- if (rd_kafka_connect(rk) == -1)
- sleep(1); /*Sleep between connection attempts*/
- break;
- case RD_KAFKA_STATE_CONNECTING:
- break;
- case RD_KAFKA_STATE_UP:
- /* .._wait_*() blocks for as long as the
- * state remains UP. */
- if (rk->rk_type == RD_KAFKA_PRODUCER)
- rd_kafka_wait_op(rk);/* 对于生产者,需要执行rd_kafka_wait_op操作 */
- else
- rd_kafka_consumer_wait_io(rk);
- break;
- }
- }
- rd_kafka_destroy(rk);
- return NULL;
- }
继续追踪rd_kafka_wait_op函数。
点击(此处)折叠或打开
- /**
- * Producer: Wait for PRODUCE events from application.
- *
- * Locality: Kafka thread
- */
- static void rd_kafka_wait_op (rd_kafka_t *rk) {
- /* 循环取消息,发送消息,销毁消息 */
- while (!rk->rk_terminate && rk->rk_state == RD_KAFKA_STATE_UP) {
- rd_kafka_op_t *rko =
- rd_kafka_q_pop(&rk->rk_op, RD_POLL_INFINITE);
- rd_kafka_produce_send(rk, rko);
- rd_kafka_op_destroy(rk, rko);
- }
- }
至此,一个异步的消息处理接口已经活生生的展现在大家的面前:
应用程序中的线程负责向队列扔消息,接口启动的线程负责循环从队列里取消息并向kafka broker发送消息。
既然有了多线程对队列的操作,异步操作,自然就会有了锁、条件变量的身影。
创建工作完成了,接下来就是应用生产消息了,这里没有特别之处,就是向队列里扔数据。
点击(此处)折叠或打开
- /**
- * Produce one single message and send it off to the broker.
- *
- * See rdkafka.h for 'msgflags'.
- *
- * Locality: application thread
- */
- void rd_kafka_produce (rd_kafka_t *rk, char *topic, uint32_t partition,
- int msgflags,
- char *payload, size_t len) {
- rd_kafka_op_t *rko;
- /* 分配空间 */
- rko = calloc(1, sizeof(*rko));
- rko->rko_type = RD_KAFKA_OP_PRODUCE;
- rko->rko_topic = topic;
- rko->rko_partition = partition;
- rko->rko_flags |= msgflags;
- rko->rko_payload = payload;
- rko->rko_len = len;
- /* 消息入队 */
- rd_kafka_q_enq(&rk->rk_op, rko);
- }
入队操作也是常规操作:
点击(此处)折叠或打开
- /**
- * Enqueue the 'rko' op at the tail of the queue 'rkq'.
- *
- * Locality: any thread.
- */
- static inline void rd_kafka_q_enq (rd_kafka_q_t *rkq, rd_kafka_op_t *rko) {
- /* 上面讲到过,入队和出队是不同的线程来完成的,因此需要加锁 */
- pthread_mutex_lock(&rkq->rkq_lock);
- TAILQ_INSERT_TAIL(&rkq->rkq_q, rko, rko_link);
- (void)rd_atomic_add(&rkq->rkq_qlen, 1);
- /* 有数据入队了,通知等待线程 */
- pthread_cond_signal(&rkq->rkq_cond);
- pthread_mutex_unlock(&rkq->rkq_lock);
- }
三、消息处理的细节
3.1 消息存储
尾队列存储
3.2 消息操作接口
入队:
点击(此处)折叠或打开
- /**
- * Enqueue the 'rko' op at the tail of the queue 'rkq'.
- *
- * Locality: any thread.
- */
- static inline void rd_kafka_q_enq (rd_kafka_q_t *rkq, rd_kafka_op_t *rko) {
- pthread_mutex_lock(&rkq->rkq_lock);
- TAILQ_INSERT_TAIL(&rkq->rkq_q, rko, rko_link);
- (void)rd_atomic_add(&rkq->rkq_qlen, 1);
- pthread_cond_signal(&rkq->rkq_cond);
- pthread_mutex_unlock(&rkq->rkq_lock);
- }
出队:
点击(此处)折叠或打开
- /**
- * Pop an op from a queue.
- *
- * Locality: any thread.
- */
- static rd_kafka_op_t *rd_kafka_q_pop (rd_kafka_q_t *rkq, int timeout_ms) {
- rd_kafka_op_t *rko;
- rd_ts_t last;
- pthread_mutex_lock(&rkq->rkq_lock);
- while (!(rko = TAILQ_FIRST(&rkq->rkq_q)) &&
- (timeout_ms == RD_POLL_INFINITE || timeout_ms > 0)) {
- if (timeout_ms != RD_POLL_INFINITE) {
- last = rd_clock();
- if (pthread_cond_timedwait_ms(&rkq->rkq_cond,
- &rkq->rkq_lock,
- timeout_ms) ==
- ETIMEDOUT) {
- pthread_mutex_unlock(&rkq->rkq_lock);
- return NULL;
- }
- timeout_ms -= (rd_clock() - last) / 1000;
- } else
- pthread_cond_wait(&rkq->rkq_cond, &rkq->rkq_lock);
- }
- if (rko) {
- TAILQ_REMOVE(&rkq->rkq_q, rko, rko_link);
- (void)rd_atomic_sub(&rkq->rkq_qlen, 1);
- }
- pthread_mutex_unlock(&rkq->rkq_lock);
- return rko;
- }
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系统调用
点击(此处)折叠或打开
- static int rd_kafka_send (rd_kafka_t *rk, const struct msghdr *msg) {
- int r;
- r = sendmsg(rk->rk_broker.s, msg, 0);
- if (r == -1) {
- rd_kafka_fail(rk, "Send failed: %s", strerror(errno));
- return -1;
- }
- rk->rk_broker.stats.tx_bytes += r;
- rk->rk_broker.stats.tx++;
- return r;
- }
四、生产者接口存在的问题与解决办法
从上面的分析可以看出,librdkafka c接口接收消息和处理消息是异步的,换句话说,生产者接口存在
问题1: 应用程序调用生产者接口时,是无法马上得知消息是否发送成功的。
另外,如果kafka broker出现了不可用等情况,kafka c接口依然会不断接收数据,存入队列,但队列没有消费者,导致OOM的风险
问题2:当kafka broker不可用时,kafka c接口的消息队列不断扩大,内存消耗一直增长。
受限于kafka当前的通信协议,应用程序暂时无法得知消息是否发送成功的,但我们还是有办法来解决这个问题(不优雅的解决办法)
生产者接口最终都是通过调用rd_kafka_send来发送消息的。
点击(此处)折叠或打开
- static int rd_kafka_send (rd_kafka_t *rk, const struct msghdr *msg) {
- int r;
- r = sendmsg(rk->rk_broker.s, msg, 0);
- if (r == -1) {
- rd_kafka_fail(rk, "Send failed: %s", strerror(errno));
- return -1;
- }
- rk->rk_broker.stats.tx_bytes += r;
- rk->rk_broker.stats.tx++;
- return r;
- }
当发送消息失败时,会调用rd_kafka_fail函数来处理发送失败的情况。
点击(此处)折叠或打开
- /**
- * Failure propagation to application.
- * Will tear down connection to broker and trigger a reconnect.
- *
- * If 'fmt' is NULL nothing will be logged or propagated to the application.
- *
- * Locality: Kafka thread
- */
- static void rd_kafka_fail (rd_kafka_t *rk, const char *fmt, ...) {
- va_list ap;
- pthread_mutex_lock(&rk->rk_lock);
- rk->rk_err.err = errno;
- rd_kafka_set_state(rk, RD_KAFKA_STATE_DOWN);
- if (rk->rk_broker.s != -1) {
- close(rk->rk_broker.s);
- rk->rk_broker.s = -1;
- }
- if (fmt) {
- va_start(ap, fmt);
- vsnprintf(rk->rk_err.msg, sizeof(rk->rk_err.msg), fmt, ap);
- va_end(ap);
- rd_kafka_log(rk, LOG_ERR, "FAIL", "%s", rk->rk_err.msg);
- /* Send ERR op back to application for processing. */
- rd_kafka_op_reply(rk, RD_KAFKA_OP_ERR,
- RD_KAFKA_RESP_ERR__FAIL, 0,
- strdup(rk->rk_err.msg),
- strlen(rk->rk_err.msg), 0);
- }
- pthread_mutex_unlock(&rk->rk_lock);
- }
从这里可以看到,当发送消息失败时,接口会通过rd_kafka_set_state函数设置kafka为RD_KAFKA_STATE_DOWN状态。
在应用程序中通过判断rk的状态可以得知kafka的当前状态。c接口封装好了访问当前状态的宏。
点击(此处)折叠或打开
- #define rd_kafka_state(rk) ((rk)->rk_state)
当应用程序检测到kafka状态为DOWN时,就不再通过c接口向kafka发送消息,这样就可以保证不出现OOM的问题。
这样是否解决了所有问题了呢?
答案是否定的。在实施过程中,笔者发现如果kafka broker不可用时,c接口在第一次获知这个不幸之前(通过sendmsg失败?),依然会向broker发送数据,而向一个已经关闭的socket发送数据,会收到对方的rst信号,这将在应用程序中产生SIGPIPE信号,如果不处理,应用程序也会异常退出!!!
比较简单粗暴的处理办法是:
点击(此处)折叠或打开
- sigemptyset(&set);
- sigaddset(&set, SIGPIPE);
- sigprocmask(SIG_BLOCK, &set, NULL);
转载:kafka c接口librdkafka介绍之二:生产者接口的更多相关文章
- C#的接口基础教程之二 定义接口
定义接口 从技术上讲,接口是一组包含了函数型方法的数据结构.通过这组数据结构,客户代码可以调用组件对象的功能. 定义接口的一般形式为: [attributes] [modifiers] interfa ...
- Kafka设计解析(一)Kafka背景及架构介绍
转载自 技术世界,原文链接 Kafka设计解析(一)- Kafka背景及架构介绍 本文介绍了Kafka的创建背景,设计目标,使用消息系统的优势以及目前流行的消息系统对比.并介绍了Kafka的架构,Pr ...
- Kafka剖析:Kafka背景及架构介绍
<Kafka剖析:Kafka背景及架构介绍> <Kafka设计解析:Kafka High Availability(上)> <Kafka设计解析:Kafka High A ...
- APP接口自动化测试JAVA+TestNG(二)之TestNG简介与基础实例
前言 继上篇环境篇后,本篇主要对TestNG进行介绍,给出最最基础的两个实例,通过本文后,学会并掌握TestNG测试用例的编写与运行,以及生成美化后的报告.下一篇为HTTP接口实战(国家气象局接口自动 ...
- Oracle EBS WMS功能介绍(二)
Oracle EBS WMS功能介绍(二) (版权声明,本人原创或者翻译的文章如需转载,如转载用于个人学习,请注明出处.否则请与本人联系,违者必究) 出货物流逻辑主要包括 1. 打包.能够进 ...
- 上门洗车APP --- Androidclient开发 之 网络框架封装介绍(二)
上门洗车APP --- Androidclient开发 之 网络框架封装介绍(二) 前几篇博文中给大家介绍了一下APP中的基本业务及开发本项目使用的网络架构: 上门洗车APP --- Androidc ...
- kafka集群原理介绍
目录 kafka集群原理介绍 (一)基础理论 二.配置文件 三.错误处理 kafka集群原理介绍 @(博客文章)[kafka|大数据] 本系统文章共三篇,分别为 1.kafka集群原理介绍了以下几个方 ...
- cWeb开发框架,基于asp.net的cWeb应用开发平台介绍(二)
cWeb是基于微软的.Net Framework 4框架,数据库是sql server 2008 r2. cWeb开发框架下载,点击这里去下载. cWeb开发框架借鉴三层架构理论分为三层,分别是:cD ...
- kafka C客户端librdkafka producer源码分析
from:http://www.cnblogs.com/xhcqwl/p/3905412.html kafka C客户端librdkafka producer源码分析 简介 kafka网站上提供了C语 ...
随机推荐
- Android -- com.android.providers.media,external.db
external.db android是管理多媒体文件(音频.视频.图片)的信息是在/data/data/com.android.providers.media下的数据库文件external.db. ...
- Topic Model的分类和设计原则
Topic Model的分类和设计原则 http://blog.csdn.net/xianlingmao/article/details/7065318 topic model的介绍性文章已经很多,在 ...
- zookeeper基础知识整理
http://blog.csdn.net/pelick/article/details/7269670 http://zookeeper.apache.org/doc/trunk/javaExampl ...
- Kafka:ZK+Kafka+Spark Streaming集群环境搭建(八)安装zookeeper-3.4.12
如何搭建配置centos虚拟机请参考<Kafka:ZK+Kafka+Spark Streaming集群环境搭建(一)VMW安装四台CentOS,并实现本机与它们能交互,虚拟机内部实现可以上网.& ...
- 为Subline Text 3 添加支持ini文件语法高亮
转: http://lancelot.blog.51cto.com/393579/1783653 在Subline text 官网下载了Subline text 3 .不过发现没有对ini格式文件的语 ...
- [Python]多线程入门
原文:http://blog.csdn.net/ice110956/article/details/28421807 Python的多线程有两种实现方法: 函数,线程类 1.函数 调用thread模块 ...
- Pandas DataFrame笔记
1.属性方式,可以用于列,不能用于行 2.可以用整数切片选择行,但不能用单个整数索引(当索引不是整数时) 3.直接索引可以使用列.列集合,但不能用索引名索引行 用iloc取行,得到的series: d ...
- hdu Boring count(BestCode round #11)
Boring count Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tot ...
- ccc如何在一台windows主机上搭建mysql主从复制
参考:http://www.cnblogs.com/wzjbk/p/6266899.htmlc 进入mysql: 进入到mysql的bin目录下才可以输入 mysql -hlocalhost -uro ...
- MYSQL判断不存在时创建表或创建数据库
创建数据库: Create Database If Not Exists MyDB Character Set UTF8 创建数据表: Create Table If Not Exists `worl ...