转发请注明原创地址http://www.cnblogs.com/dongxiao-yang/p/6234673.html

最近业务同学反馈kafka上线的时候某个topic的部分分区一直没有owner注册上,监控界面形式如图,其中分区5和7无法被消费者注册到,重启客户端程序rebalance依旧是这两个分区没有被消费。

由于最近业务方机房大迁移,第一反应是网络连通性,但是消费端程序挨个测试网络没有问题,而且即使通过增加或者减少consumer数量,甚至消费端只开一个客户端,rebalance结束后依然会有分区没有owner,而且随着消费端个数的变化,无owner的分区号也发生了变化,整个rebalance过程客户端程序没有任何错误日志。

这种情况还得去过客户端日志,在只起了两个客户端的时候发现有这么一段:


16/12/29 15:52:56 INFO consumer.ZookeeperConsumerConnector: [xxx], Consumer xxx rebalancing the following partitions: ArrayBuffer(5, 7, 3, 8, 1, 4, 6, 2, 0, 9) for topic onlineAdDemographicPredict with consumers: List(aaa-0, yyy-0, xxx-0)
16/12/29 15:52:56 INFO consumer.ZookeeperConsumerConnector: [xxx], xxx-0 attempting to claim partition 2
16/12/29 15:52:56 INFO consumer.ZookeeperConsumerConnector: [xxx], xxx-0 attempting to claim partition 0
16/12/29 15:52:56 INFO consumer.ZookeeperConsumerConnector: [xxx], xxx-0 attempting to claim partition 9
16/12/29 15:52:56 INFO consumer.ZookeeperConsumerConnector: [xxx], xxx-0 successfully owned partition 0 for topic onlineAdDemographicPredict
16/12/29 15:52:56 INFO consumer.ZookeeperConsumerConnector: [xxx], xxx-0 successfully owned partition 9 for topic onlineAdDemographicPredict
16/12/29 15:52:56 INFO consumer.ZookeeperConsumerConnector: [xxx], xxx-0 successfully owned partition 2 for topic onlineAdDemographicPredict

 

ArrayBuffer里分区10个分区都全了说明客户端读取所有Partirtion个数是没有问题的,出问题的是with consumers: List()这个信息,此时业务方只起了xxx和yyy两个客户端,

但是Consumer确拿到了三个client-id,然后经过计算自己正好需要注册三个分区2,0,9,剩下的分区就没人认领了。

查找日志对应kafka源码如下

class RangeAssignor() extends PartitionAssignor with Logging {

  def assign(ctx: AssignmentContext) = {
val partitionOwnershipDecision = collection.mutable.Map[TopicAndPartition, ConsumerThreadId]() for ((topic, consumerThreadIdSet) <- ctx.myTopicThreadIds) {
val curConsumers = ctx.consumersForTopic(topic)
val curPartitions: Seq[Int] = ctx.partitionsForTopic(topic) val nPartsPerConsumer = curPartitions.size / curConsumers.size
val nConsumersWithExtraPart = curPartitions.size % curConsumers.size info("Consumer " + ctx.consumerId + " rebalancing the following partitions: " + curPartitions +
" for topic " + topic + " with consumers: "
+ curConsumers) for (consumerThreadId <- consumerThreadIdSet) {
val myConsumerPosition = curConsumers.indexOf(consumerThreadId)
assert(myConsumerPosition >= )
val startPart = nPartsPerConsumer * myConsumerPosition + myConsumerPosition.min(nConsumersWithExtraPart)
val nParts = nPartsPerConsumer + (if (myConsumerPosition + > nConsumersWithExtraPart) else ) /**
* Range-partition the sorted partitions to consumers for better locality.
* The first few consumers pick up an extra partition, if any.
*/
if (nParts <= )
warn("No broker partitions consumed by consumer thread " + consumerThreadId + " for topic " + topic)
else {
for (i <- startPart until startPart + nParts) {
val partition = curPartitions(i)
info(consumerThreadId + " attempting to claim partition " + partition)
// record the partition ownership decision
partitionOwnershipDecision += (TopicAndPartition(topic, partition) -> consumerThreadId)
}
}
}
} partitionOwnershipDecision
}
}
object PartitionAssignor {
def createInstance(assignmentStrategy: String) = assignmentStrategy match {
case "roundrobin" => new RoundRobinAssignor()
case _ => new RangeAssignor()
}
} class AssignmentContext(group: String, val consumerId: String, excludeInternalTopics: Boolean, zkClient: ZkClient) {
val myTopicThreadIds: collection.Map[String, collection.Set[ConsumerThreadId]] = {
val myTopicCount = TopicCount.constructTopicCount(group, consumerId, zkClient, excludeInternalTopics)
myTopicCount.getConsumerThreadIdsPerTopic
} val partitionsForTopic: collection.Map[String, Seq[Int]] =
ZkUtils.getPartitionsForTopics(zkClient, myTopicThreadIds.keySet.toSeq) val consumersForTopic: collection.Map[String, List[ConsumerThreadId]] =
ZkUtils.getConsumersPerTopic(zkClient, group, excludeInternalTopics)
val consumers: Seq[String] = ZkUtils.getConsumersInGroup(zkClient, group).sorted
}
class ZKGroupDirs(val group: String) {
def consumerDir = ConsumersPath
def consumerGroupDir = consumerDir + "/" + group
def consumerRegistryDir = consumerGroupDir + "/ids"
def consumerGroupOffsetsDir = consumerGroupDir + "/offsets"
def consumerGroupOwnersDir = consumerGroupDir + "/owners"
} def getConsumersPerTopic(group: String, excludeInternalTopics: Boolean): mutable.Map[String, List[ConsumerThreadId]] = {
val dirs = new ZKGroupDirs(group)
val consumers = getChildrenParentMayNotExist(dirs.consumerRegistryDir)
val consumersPerTopicMap = new mutable.HashMap[String, List[ConsumerThreadId]]
for (consumer <- consumers) {
val topicCount = TopicCount.constructTopicCount(group, consumer, this, excludeInternalTopics)
for ((topic, consumerThreadIdSet) <- topicCount.getConsumerThreadIdsPerTopic) {
for (consumerThreadId <- consumerThreadIdSet)
consumersPerTopicMap.get(topic) match {
case Some(curConsumers) => consumersPerTopicMap.put(topic, consumerThreadId :: curConsumers)
case _ => consumersPerTopicMap.put(topic, List(consumerThreadId))
}
}
}
for ( (topic, consumerList) <- consumersPerTopicMap )
consumersPerTopicMap.put(topic, consumerList.sortWith((s,t) => s < t))
consumersPerTopicMap
}
 def constructTopicCount(group: String, consumerId: String, zkUtils: ZkUtils, excludeInternalTopics: Boolean) : TopicCount = {
val dirs = new ZKGroupDirs(group)
val topicCountString = zkUtils.readData(dirs.consumerRegistryDir +
"/" + consumerId)._1
var subscriptionPattern: String = null
var topMap: Map[String, Int] = null
try {
Json.parseFull(topicCountString) match {
case Some(m) =>
val consumerRegistrationMap = m.asInstanceOf[Map[String, Any]]
consumerRegistrationMap.get("pattern") match {
case Some(pattern) => subscriptionPattern = pattern.asInstanceOf[String]
case None => throw new KafkaException("error constructing TopicCount : " + topicCountString)
}
consumerRegistrationMap.get("subscription") match {
case Some(sub) => topMap = sub.asInstanceOf[Map[String, Int]]
case None => throw new KafkaException("error constructing TopicCount : " + topicCountString)
}
case None => throw new KafkaException("error constructing TopicCount : " + topicCountString)
}
} catch {
case e: Throwable =>

    

通过上面着色的代码一路跟下来,可以看出来Consumer获取group所有客户端数量逻辑是读取zk上 /kafkachroot/consumers/{groupid}/ids路径下

所有存在的consumerid,然后读取这些consumerid对应的topic信息,最终返回一个[topic, List[ConsumerThreadId]] 的二维数组。

于是跑到zk上看节点结构,发现在出问题的group/ids 路径下果然存在aaa这个临时节点,通知应用方发现原来有个很老的程序之前也用同样的groupid消费过这个topic,但是现在业务程序很久没人管处在一个半假死的状态,所以这个临时节点一直不过期,导致后来使用同样group消费同样的每次都会感知到一个多余的消费段存在,所以每次都有部分分区无法被消费。

附:

1  Consumer Rebalance的算法

2  本文讨论的版本建立在kafka 0.8.2-beta版本前提上,新出的版本目前没有研究,可能情况不符。

kafka rebalance 部分分区没有owner的更多相关文章

  1. kafka rebalance解决方案 -incremental cooperative协议和static membership功能

    apache kafka的重平衡(rebalance),一直以来都为人诟病.因为重平衡过程会触发stop-the-world(STW),此时对应topic的资源都会处于不可用的状态.小规模的集群还好, ...

  2. (一)kafka修改topic分区的位置

    (一)kafka修改topic分区的位置 环境:kafka_2.10-0.8.2.1 + JDK1.7.0_80 1. 查看分区topic的分区分布 $ le-kafka-topics.sh --de ...

  3. kafka partition(分区)与 group

    kafka partition(分区)与 group   一. 1.原理图 2.原理描述 一个topic 可以配置几个partition,produce发送的消息分发到不同的partition中,co ...

  4. 玩转Kafka的生产者——分区器与多线程

    上篇文章学习kafka的基本安装和基础概念,本文主要是学习kafka的常用API.其中包括生产者和消费者, 多线程生产者,多线程消费者,自定义分区等,当然还包括一些避坑指南. 首发于个人网站:链接地址 ...

  5. kafka之partition分区及副本replica升级

    修改kafka的partition分区 bin/kafka-topics.sh --zookeeper datacollect-2:2181 --alter --partitions 3 --topi ...

  6. 【Kafka】数据分区策略

    数据分区策略 四种策略 一.指定分区号,数据会直接发送到所指定的分区 二.没有指定分区号,指定了数据的key,可以通过key获取hashCode决定数据发送到哪个分区 三.都没有指定的话,会采取rou ...

  7. Kafka Rebalance机制分析

    什么是 Rebalance Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 consumer 如何达成一致,来分配订阅 Topic 的每个分区. 例如:某 G ...

  8. 什么是 Kafka Rebalance 以及关于 Rebalance Kafka-Python 社区客户端应该关注的地方

    什么是 Rebalance? Rebalance 为什么会发生?Rebalance 的情况下 consumer 是否还能正确消费消息呢? 记得之前在一段时间密集面试的时候总会问候选人这些问题. 重平衡 ...

  9. Kafka Rebalance机制和选举策略总结

    自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客同步发表文章,为了更好的阅读体验,建议您移步至我的博客 本文作者: Jeffrey 本文链接: https://w ...

随机推荐

  1. 【bzoj】1026: [SCOI2009]windy数

    1026: [SCOI2009]windy数 Description windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道,在A和B之间 ...

  2. HttpContext.Current.Session=null问题

    启用asp.net状态服务,可以让Session持久化!

  3. Js处理数据——前端分页工具

    这几天有小伙伴讨论起了分页的相关问题,这里我也简单讲下前端如何简单便捷的利用Js(库)写出优雅,好用的分页工具. 分页是个很简单又超多接触的技术点,粗略来讲分如下两种: 真分页--每次根据页码.页大小 ...

  4. 快速发展的Swift是否将淘汰Objective-C?

    随便拉上一个果粉说说这一年来苹果公司的成就,Apple Watch肯定排第一,再下来是iPhone破销量纪录及苹果30亿美元收购Beats Electronics,消息灵通的人说不定还会提到Apple ...

  5. 使用SeaJS实现模块化JavaScript开发(新)

    本文转自张洋,因为SeaJS更新版本很快,所以原文中很多地方不太适用,在这里发布一个更新版. 前言   SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaSc ...

  6. C++的数据类型

    C++的数据类型 计算机处理的对象是数据,而数据是以某种特定的形式存在的(例如整数.浮点数.字符等形式). 不同的数据之间往往还存在某些联系(例如由若干个整数组成一个整数数组).数据结构指的是数据的组 ...

  7. 小波变换 C++ opencv 实现

    小波变换 C++ opencv 实现 小波简介: http://www.blogbus.com/shijuanfeng-logs/221293135.html 源码:   /// 小波变换 Mat W ...

  8. spring初始化

    * Created by litao on 15/12/29. */@Component("initTagDataProcessor")public class InitTagDa ...

  9. [转贴] 从零开始学C++之异常(二):程序错误、异常(语法、抛出、捕获、传播)、栈展开

    一.程序错误 编译错误,即语法错误.程序就无法被生成运行代码. 运行时错误 不可预料的逻辑错误 可以预料的运行异常 例如: 动态分配空间时可能不会成功 打开文件可能会失败 除法运算时分母可能为0 整数 ...

  10. C#功能扩张方式

    有时候,为了完成一些window的一些操作,需要引入一些dll进行操作 1)  引入系统api进行操作,形如[DllImport("urlmon.dll", CharSet = C ...