1 背景

最近由于项目需要,需要使用kafka的producer。但是对于c++,kafka官方并没有很好的支持。

在kafka官网上可以找到0.8.x的客户端。可以使用的客户端有C版本客户端,此客户端虽然目前看来还较为活跃,但是代码问题还是较多的,而且对于c++的支持并不是很好。

还有c++版本,虽然该客户端是按照c++的思路设计,但是最近更新时间为2013年12月19日,已经很久没有更新了。

从官方了解到,kafka作者对于现有的producer和consumer的设计是不太满意的。他们打算在kafka 0.9版本里发布新的producer与consumer。

其中新的producer已经被包含到了kafka0.8.1的源码里,官方描述如下。

3.4 New Producer Configs

We are working on a replacement for our existing producer. The code is available in trunk now and can be considered beta quality. Below is the configuration for the new producer

现在新producer还是属于beta版。但是在kafka0.9版本里,新producer与consumer都会成为稳定版,并提供了更多的功能。旧版的producer是由scala实现,为java提供调用api。而新版的producer直接是用java实现的。

具体文档在这https://cwiki.apache.org/confluence/display/KAFKA/Client+Rewrite

2 producer基本类的介绍

源码树如下

其中,org.apache.kafka.clients.tools包下的ProducerPerformance.java里包含了producer的最基本用法。

该程序原本是有三个参数的,直接给三个参数硬编码赋值后,代码如下:

  1. public static void main(String[] args) throws Exception {
  2. String url = "10.134.58.155:9092";
  3. int numRecords = 100;
  4. int recordSize = 100;
  5. Properties props = new Properties();
  6. props.setProperty(ProducerConfig.REQUIRED_ACKS_CONFIG, "1");
  7. props.setProperty(ProducerConfig.BROKER_LIST_CONFIG, url);
  8. props.setProperty(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG, Integer.toString(5 * 1000));
  9. props.setProperty(ProducerConfig.REQUEST_TIMEOUT_CONFIG, Integer.toString(Integer.MAX_VALUE));
  10.  
  11. KafkaProducer producer = new KafkaProducer(props);
  12. Callback callback = new Callback() {
  13. public void onCompletion(RecordMetadata metadata, Exception e) {
  14. if (e != null)
  15. e.printStackTrace();
  16. }
  17. };
  18. byte[] payload = new byte[recordSize];
  19. Arrays.fill(payload, (byte) 1);
  20. ProducerRecord record = new ProducerRecord("test6", payload);
  21. long start = System.currentTimeMillis();
  22. long maxLatency = -1L;
  23. long totalLatency = 0;
  24. int reportingInterval = 1;
  25. for (int i = 0; i < numRecords; i++) {
  26. long sendStart = System.currentTimeMillis();
  27. producer.send(record, callback);
  28. long sendEllapsed = System.currentTimeMillis() - sendStart;
  29. maxLatency = Math.max(maxLatency, sendEllapsed);
  30. totalLatency += sendEllapsed;
  31. if (i % reportingInterval == 0) {
  32. System.out.printf("%d max latency = %d ms, avg latency = %.5f\n",
  33. i,
  34. maxLatency,
  35. (totalLatency / (double) reportingInterval));
  36. totalLatency = 0L;
  37. maxLatency = -1L;
  38. }
  39. }
  40. long ellapsed = System.currentTimeMillis() - start;
  41. double msgsSec = 1000.0 * numRecords / (double) ellapsed;
  42. double mbSec = msgsSec * (recordSize + Records.LOG_OVERHEAD) / (1024.0 * 1024.0);
  43. System.out.printf("%d records sent in %d ms ms. %.2f records per second (%.2f mb/sec).", numRecords, ellapsed, msgsSec, mbSec);
  44. producer.close();
  45. }

可以看到,运行producer需要三个基本的类ProducerConfig,KafkaProducer,ProducerRecord,另外还有回调函数的类Callback。

ProducerConfig类包含了kafka的各种配置信息,并提供了默认的配置。

ProducerRecord类是向broker发送的消息载体,包括topic,partition,key和value属性。

上面这两个类都很简单。

producer所有操作都包含在KafkaProducer类中。

这个类由Partitioner,Metadata,RecordAccumulator,Sender,Metrics这些类组成。

Partitioner是用来计算一个消息的分片的类。

Metadata顾名思义保存的是kafka集群的元数据,metadata的更新和topic有关。

RecordAccumulator类似于一个队列,所有producer发出的消息都先送到队列中,等待处理。

Sender类使用NIO方式实现了producer消息的发送与接收,sender是一个守护线程,监听读写事件,并

Metrics类,kafka本来是被用于分布式的日志收集与监控的,Metrics类可以注册一些关注的内容,供监控使用。

3源码分析

我们以发送一条消息来分析producer的工作过程。

发送一条消息可以分为异步的两个过程。

入队过程

  1. @Override
  2. public Future<RecordMetadata> send(ProducerRecord record, Callback callback) {
  3. try {
  4. Cluster cluster = metadata.fetch(record.topic(), this.metadataFetchTimeoutMs);
  5. int partition = partitioner.partition(record, cluster);
  6. ensureValidSize(record.key(), record.value());
  7. TopicPartition tp = new TopicPartition(record.topic(), partition);
  8. FutureRecordMetadata future = accumulator.append(tp, record.key(), record.value(), CompressionType.NONE, callback);
  9. this.sender.wakeup();
  10. return future;
  11. } catch (Exception e) {
  12. if (callback != null)
  13. callback.onCompletion(null, e);
  14. return new FutureFailure(e);
  15. }
  16. }

该send函数首先根据topic获取集群的基本数据,如果topic不存在,该函数会阻塞,并更新metadata。

接下来获取分区,并将数据写入该TopicPartition下的队列中。

  1. public FutureRecordMetadata append(TopicPartition tp, byte[] key, byte[] value, CompressionType compression, Callback callback) throws InterruptedException {
  2. if (closed)
  3. throw new IllegalStateException("Cannot send after the producer is closed.");
  4. // check if we have an in-progress batch
  5. Deque<RecordBatch> dq = dequeFor(tp);
  6. synchronized (dq) {
  7. RecordBatch batch = dq.peekLast();
  8. if (batch != null) {
  9. FutureRecordMetadata future = batch.tryAppend(key, value, compression, callback);
  10. if (future != null)
  11. return future;
  12. }
  13. }
  14.  
  15. // we don't have an in-progress record batch try to allocate a new batch
  16. int size = Math.max(this.batchSize, Records.LOG_OVERHEAD + Record.recordSize(key, value));
  17. ByteBuffer buffer = free.allocate(size);
  18. synchronized (dq) {
  19. RecordBatch first = dq.peekLast();
  20. if (first != null) {
  21. FutureRecordMetadata future = first.tryAppend(key, value, compression, callback);
  22. if (future != null) {
  23. // Somebody else found us a batch, return the one we waited for! Hopefully this doesn't happen
  24. // often...
  25. free.deallocate(buffer);
  26. return future;
  27. }
  28. }
  29. RecordBatch batch = new RecordBatch(tp, new MemoryRecords(buffer), time.milliseconds());
  30. FutureRecordMetadata future = Utils.notNull(batch.tryAppend(key, value, compression, callback));
  31. dq.addLast(batch);
  32. return future;
  33. }
  34. }

这个函数上面有一大段关于send函数的用法,简单来说,send函数可以实现简单的阻塞式发送(利用Future.get()方法),以及利用回调函数,实现非阻塞发送。

因为这个是一个向套接字写数据的过程,所以入队之后,立刻调用wakeup函数,唤醒阻塞在读数据的sender上,并发送数据。

出队过程

该过程是由守护线程完成的,守护线程不断循环在run函数上

  1. public int run(long now) {
  2. Cluster cluster = metadata.fetch();
  3. // get the list of partitions with data ready to send
  4. List<TopicPartition> ready = this.accumulator.ready(now);
  5.  
  6. // prune the list of ready topics to eliminate any that we aren't ready to send yet
  7. List<TopicPartition> sendable = processReadyPartitions(cluster, ready, now);
  8.  
  9. // should we update our metadata?
  10. List<NetworkSend> sends = new ArrayList<NetworkSend>(sendable.size());
  11. InFlightRequest metadataReq = maybeMetadataRequest(cluster, now);
  12. if (metadataReq != null) {
  13. sends.add(metadataReq.request);
  14. this.inFlightRequests.add(metadataReq);
  15. }
  16.  
  17. // create produce requests
  18. List<RecordBatch> batches = this.accumulator.drain(sendable, this.maxRequestSize);
  19. List<InFlightRequest> requests = collate(cluster, batches);
  20. for (int i = 0; i < requests.size(); i++) {
  21. InFlightRequest request = requests.get(i);
  22. this.inFlightRequests.add(request);
  23. sends.add(request.request);
  24. }
  25.  
  26. // do the I/O
  27. try {
  28. this.selector.poll(5L, sends);
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32.  
  33. // handle responses, connections, and disconnections
  34. handleSends(this.selector.completedSends());
  35. handleResponses(this.selector.completedReceives(), now);
  36. handleDisconnects(this.selector.disconnected());
  37. handleConnects(this.selector.connected());
  38.  
  39. return ready.size();
  40. }

代码注释很清晰了。。

handleSends实现了入队过程中的future以及回调。

后续的一些对网络协议的封装就不再赘述。下一篇,我会接着分析kafka producer的c客户端librdkafka

第一次写博客或许写的不是很清楚,望大家可以多提提意见,谢谢。

kafka 0.8.1 新producer 源码简单分析的更多相关文章

  1. negroni-gzip源码简单分析解读

    negroni-gzip源码简单分析解读 这是一个为Negroni设计的gzip压缩处理中间件,需要用到已有的compress中的gzip,阅读了不长的源码之后,总结了一些关键要点和注意点. 检查是否 ...

  2. FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  3. FFmpeg源码简单分析:libswscale的sws_scale()

    ===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFm ...

  4. FFmpeg源码简单分析:结构体成员管理系统-AVOption

    ===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFm ...

  5. FFmpeg的HEVC解码器源码简单分析:解码器主干部分

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  6. Django-session中间件源码简单分析

    Django-session中间件源码简单分析 settings里有关中间件的配置 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddlew ...

  7. FFmpeg的HEVC解码器源码简单分析:概述

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  8. urllib源码简单分析

    对下面这段代码做分析 import urllib params = urllib.urlencode({'wd': 'python'}) f = urllib.urlopen("http:/ ...

  9. CardboardSDK-iOS 源码简单分析

    该项目地址: 地址 克隆地址为 https://github.com/rsanchezsaez/CardboardSDK-iOS.git 目前如果想在iOS设备上实现双目VR的功能,Google 已经 ...

随机推荐

  1. Mac下jdk多版本管理

    网上试了.bash_profile中增加路径设置别名的方法,但是始终无法切换,只能使用jenv了. 1. 下载 jenv(来自官网) git clone https://github.com/gcui ...

  2. CentOS7.0使用Yum安装Nginx

    安装Nginx yum install nginx 正常情况下必定是: 已加载插件:fastestmirror, langpacks base | 3.6 kB 00:00:00 docker-mai ...

  3. AC日记——[Wc2008]游览计划 bzoj 2595

    2595 思路: 状压DP+spfa转移+dfs输出路径: 或者,斯坦纳树算法模板: 来,上代码: #include <queue> #include <cstdio> #in ...

  4. 陕西师范大学第七届程序设计竞赛网络同步赛D ZQ的睡前故事【约瑟夫环1-N数到第k个出队,输出出队顺序/ STL模拟】

    链接:https://www.nowcoder.com/acm/contest/121/D来源:牛客网 题目描述 ZQ是一个拥有n女朋友的万人迷,她的每一个女朋友每天晚上都会挨个给他打电话,要他讲了睡 ...

  5. Code+ A 晨跑【三个数的最小公倍数】

    时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 262144K,其他语言524288K64bit IO Format: %lld 题目描述 “无体育,不清华”.“每天锻炼一小时,健康工作 ...

  6. UVA 1151 Buy or Build MST(最小生成树)

    题意: 在平面上有n个点,要让所有n个点都连通,所以你要构造一些边来连通他们,连通的费用等于两个端点的欧几里得距离的平方.另外还有q个套餐,可以购买,如果你购买了第i个套餐,该套餐中的所有结点将变得相 ...

  7. 树链剖分【p2590】[ZJOI2008]树的统计

    Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. ...

  8. (转)Unity3d各种坑

    1.unity的资源包一旦量很大的时候卸载不干净,你可以尝试反复切场景 ,内存诡异的 增加 一直到爆,assetsbundle.unload(true);有问题 你想要卸载你必须先让你加载过的资源为n ...

  9. appium 几点总结(转)

    1. 建立session时常用命令: DesiredCapabilities cap = new DesiredCapabilities(); cap.SetCapability("brow ...

  10. 线性判别分析(Linear Discriminant Analysis, LDA)算法初识

    LDA算法入门 一. LDA算法概述: 线性判别式分析(Linear Discriminant Analysis, LDA),也叫做Fisher线性判别(Fisher Linear Discrimin ...