0.前言

客户端用法:

kafka.javaapi.consumer.ConsumerConnector consumer = kafka.consumer.Consumer.createJavaConsumerConnector(new ConsumerConfig(properties));

// 决定一个topic启动几个线程去拉取数据,即生成几个KafkaStream;
Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
topicCountMap.put(topic, new Integer(threads)); Map<String, List<KafkaStream<byte[], byte[]>>> topicMessageStreams = consumer.createMessageStreams(topicCountMap);
List<KafkaStream<byte[], byte[]>> streams = topicMessageStreams.get(topic); // 本质是调用了 ZookeeperConsumerConnector
val consumerConnect = new kafka.javaapi.consumer.ZookeeperConsumerConnector(config)
  • 一个Topic启动几个消费者线程,会生成几个KafkaStream。
  • 一个KafkaStream对应的是一个Queue(有界的LinkedBlockingQueue),有界的参数控制:queued.max.message.chunks。消费者线程数量决定阻塞队列的个数。
  • Fetcher线程是对应topic所在的broker的个数。

因此,分析Consumer,主要是分析ZookeeperConsumerConnector。代码里面,有两个类,它们是什么关系呢?

  • kafka.consumer.ZookeeperConsumerConnector:核心类
  • kafka.javaapi.consumer.ZookeeperConsumerConnector:对上面那个类的scala数据结构封装,方便Java程序员使用。

0.8.0 和 0.8.2.1 ZookeeperConsumerConnector的源码不一样,下面以0.8.2.1源码为主来分析,也就是从这个版本开始,可以将Offset存在Kafka的Broker中。(关注实现思想,忽略细节。)

1.ZookeeperConsumerConnector 架构

一个Consumer会创建一个ZookeeperConsumerConnector,代表一个消费者进程.

  • fetcher: 消费者获取数据, 使用ConsumerFetcherManager fetcher线程抓取数据
  • zkClient: 消费者要和ZK通信, 除了注册自己,还有其他信息也会写到ZK中
  • topicThreadIdAndQueues: 消费者会指定自己消费哪些topic,并指定线程数, 所以topicThreadId都对应一个队列
  • messageStreamCreated: 消费者会创建消息流, 每个队列都对应一个消息流
  • offsetsChannel: offset可以存储在ZK或者kafka中,如果存在kafka里,像其他请求一样,需要和Broker通信。可以理解成OffsetManager的一部分。
  • scheduler: 后台调度autoCommit
  • 还有其他几个Listener监听器,分别用于topicPartition的更新,负载均衡,消费者重新负载等

简述获取数据的流程

  1. 初始化上面的几个组件,包括与ZK的连接,创建ConsumerFetcherManager,确保连接上OffsetManager(为该ConsumerGroup建立一个OffsetChannel)。
  2. createMessageStreams创建消息流,反序列化message
  3. 通过Fetcher线程拉取数据,放入BlockingQueue来给客户端。
  4. 客户端启动ZKRebalancerListener,ZKRebalancerListener实例会在内部创建一个线程,这个线程定时检查监听的事件有没有执行(消费者发生变化),如果没有变化则wait 1秒钟,当发生了变化就调用 syncedRebalance 方法,去rebalance消费者。

1.1 消费者线程(consumer thread),队列(LinkedBlockingQueue),拉取线程(fetch thread)三者之间关系

以一段代码来说明,消费的topic 12 partition,分配在3台broker机器上。

ConsumerConnector consumer = kafka.consumer.Consumer.createJavaConsumerConnector(createConsumerConfig());
Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
topicCountMap.put("test-string-topic", new Integer(2)); //value表示consumer thread线程数量 Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
  • consumer thread数量与BlockingQueue一一对应。所以上述的代码只有2个BlockQueue。(它们连接的桥梁是KafkaStream)
  • fetcher线程数和topic所在多少台broker有关。因此,共有3个fetcher线程与broker建立一个连接。(3个fetch thread线程去拉取消息数据,最终放到2个BlockingQueue中,等待consumer thread来消费。)

下面是分配的情况:

  • 消费者线程,缓冲队列,partitions分布列表如下
consumer线程 Blocking Queue partitions
consumer thread1 blockingQueue1 0,1,2,3,4,5
consumer thread2 blockingQueue2 6,7,8,9,10,11
  • fetch thread与partitions分布列表如下
fetch线程 partitions
fetch thread1 0,3,6,9
fetch thread2 1,4,7,10
fetch thread3 2,5,8,11

用户的consumer thread就使用2个BlockingQueue的数据进行处理;所以一般会使用2个consumer thread去消费这2个BlockingQueue数据。

1.2 rebalance的流程

代码上调用:syncedRebalance方法在内部会调用def rebalance(cluster: Cluster): Boolean方法,去执行操作。

  1. // 关闭所有的数据获取者 closeFetchers
  2. // 解除分区的所有者 releasePartitionOwnership
  3. // 按规则得到当前消费者拥有的分区信息并保存到topicRegistry中 topicRegistry=getCurrentConsumerPartitionInfo
  4. // 修改并重启Fetchers updateFetchers

最后,对每个broker创建一个FetcherRunnable线程,并启动它。这个fetcher线程负责从Broker上不断获取数据,对每个partition分别创建FetchRequest,最后把数据插入BlockingQueue的操作。

KafkaStream对ConsumerIterator做了进一步的封装,我们调用stream的next方法就可以取到数据了(内部通过调用ConsumerIterator的next方法实现)

1.3 注意

ConsumerIterator的实现可能会造成数据的重复发送(这要看生产者如何生产数据),FetchedDataChunk是一个数据集合,它内部会包含很多数据块,一个数据块可能包含多条消息,但同一个数据块中的消息只有一个offset,所以当一个消息块有多条数据,处理完部分数据发生异常时,消费者重新去取数据,就会再次取得这个数据块,然后消费过的数据就会被重新消费。

  • 没想到里面,里面是这个样子的,给一个数据块,导致了数据消费的重复。

3.美团遇到的一个问题

问题: Kafka中由Consumer维护消费状态,当Consumer消费消息时,支持2种模式commit消费状态,分别为立即commit和周期commit。前者会导致性能低下,做到消息投递恰好一次,但很少使用,后者性能高,通常用于实际应用,但极端条件下无法保证消息不丢失。

解决方案(这个问题太极端情况,不推荐,长个知识)

  • 将本来的结果改成下面的处理流程:等待“执行业务逻辑”成功完成后更新缓存消费状态,就可以保证消息不会丢失。

变成下面的:

Kafka 0.8 Consumer处理逻辑的更多相关文章

  1. Kafka 0.8 Consumer设计解析

    摘要 本文主要介绍了Kafka High Level Consumer,Consumer Group,Consumer Rebalance,Low Level Consumer实现的语义,以及适用场景 ...

  2. Kafka 0.8 Consumer Rebalance

    1 Rebalance时机 0.10kafka的rebalance条件 条件1:有新的consumer加入 条件2:旧的consumer挂了 条件3:coordinator挂了,集群选举出新的coor ...

  3. Kafka 0.8 Producer处理逻辑

    Kafka Producer产生数据发送给Kafka Server,具体的分发逻辑及负载均衡逻辑,全部由producer维护. 1.Kafka Producer默认调用逻辑 1.1 默认Partiti ...

  4. Kafka 0.9+Zookeeper3.4.6集群搭建、配置,新Client API的使用要点,高可用性测试,以及各种坑 (转载)

    Kafka 0.9版本对java client的api做出了较大调整,本文主要总结了Kafka 0.9在集群搭建.高可用性.新API方面的相关过程和细节,以及本人在安装调试过程中踩出的各种坑. 关于K ...

  5. Kafka 0.10.0

    2.1 Producer API We encourage all new development to use the new Java producer. This client is produ ...

  6. Kafka 0.8 配置参数解析

    http://kafka.apache.org/documentation.html#configuration   Broker Configs 4个必填参数, broker.id Each bro ...

  7. Kafka 0.10 KafkaConsumer流程简述

    ConsumerConfig.scala 储存Consumer的配置 按照我的理解,0.10的Kafka没有专门的SimpleConsumer,仍然是沿用0.8版本的. 1.从poll开始 消费的规则 ...

  8. 【译】Flink + Kafka 0.11端到端精确一次处理语义的实现

    本文是翻译作品,作者是Piotr Nowojski和Michael Winters.前者是该方案的实现者. 原文地址是https://data-artisans.com/blog/end-to-end ...

  9. Kafka 0.11.0.0 实现 producer的Exactly-once 语义(中文)

    很高兴地告诉大家,具备新的里程碑意义的功能的Kafka 0.11.x版本(对应 Confluent Platform 3.3)已经release,该版本引入了exactly-once语义,本文阐述的内 ...

随机推荐

  1. express框架结合ejs模板引擎使用

    我们在项目里建立一个views文件夹(必须),如果你不想使用views文件夹的话需要调用app.set("views","自定义文件夹名"),然后在里面建立一个 ...

  2. Scrum Meeting Beta - 7

    Scrum Meeting Beta - 7 NewTeam 2017/12/6 地点:新主楼F座二楼 任务反馈 团队成员 完成任务 计划任务 安万贺 修复离线状态下启动时的bugIssue #150 ...

  3. 二叉树 Java 实现 前序遍历 中序遍历 后序遍历 层级遍历 获取叶节点 宽度 ,高度,队列实现二叉树遍历 求二叉树的最大距离

    数据结构中一直对二叉树不是很了解,今天趁着这个时间整理一下 许多实际问题抽象出来的数据结构往往是二叉树的形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显 ...

  4. 重新认识javascript的settimeout和异步

    1.简单的settimeout setTimeout(function () { while (true) { } }, 1000); setTimeout(function () { alert(' ...

  5. UVA11374_Airport Express

    给一个无向图,有的边是特殊边,最多可以取一条特殊边,求最短路,并且输出路径. 这样考虑,加入所有非特殊边,求出每个点到起点和终点的最短路.然后加入特殊边的时候,如果取当前这条特殊边,那么答案会是两点预 ...

  6. POJ2391_Ombrophobic Bovines

    有F个地方,每个地方有一定数量的牛,能够容纳一定数量的牛,某些地方之间有边,表示走两点之间需要消耗的时间. 现在求使得所有的牛都被容纳所需要的最少的时间. 由于时间是一个不确定的因素,我们需要二分. ...

  7. UVAlive3523_Knights of the Round Table

    圆桌骑士.有的骑士之间是相互憎恨的,不能连坐,需要安排奇数个骑士围着桌子坐着,大于3个,求哪些骑士不可能安排到座位. 根据给定的关系,如果两个骑士之间没有憎恨关系,那么连边.最终就是求有多少个点无法位 ...

  8. 【uoj#228】基础数据结构练习题 线段树+均摊分析

    题目描述 给出一个长度为 $n$ 的序列,支持 $m$ 次操作,操作有三种:区间加.区间开根.区间求和. $n,m,a_i\le 100000$ . 题解 线段树+均摊分析 对于原来的两个数 $a$ ...

  9. Python语言算法的时间复杂度和空间复杂度

    算法复杂度分为时间复杂度和空间复杂度. 其作用: 时间复杂度是指执行算法所需要的计算工作量: 而空间复杂度是指执行这个算法所需要的内存空间. (算法的复杂性体现在运行该算法时的计算机所需资源的多少上, ...

  10. 013 BIO、NIO、AIO的区别

    作者:nnngu GitHub:https://github.com/nnngu 博客园:http://www.cnblogs.com/nnngu 简书:https://www.jianshu.com ...