问题导读:

1.kafka的消费者组的消费偏移存储,kafka支持两个版本?
        2.ConsumerOffsetChecker类的作用是什么?
        3.Kafka如何通过源码实现监控?

一,基本思路介绍
Kafka作为一个好用的且应用很广泛的消息队列,在大数据处理系统中基本是必不可少的。当然,作为缓存消息的消息队列,我们对其进行流量监控及消费滞后告警就显得异常重要了。
读过前面的文章,<Kafka源码系列之源码解析SimpleConsumer的消费过程>和<Kafka源码系列之Consumer高级API性能分析>这两篇文章的兄弟姐妹应该看本篇文章会很简单。实际就是利用SimpleConsumer获取Partition最新的offset,用Zookeeper的工具获取消费者组的各个分区的消费偏移,两者做差就是lagSize。
但是实际kafka的消费者组的消费偏移存储,kafka支持两个版本的:
1,基于Zookeeper。OffsetFetchRequest.CurrentVersion为0。
2,基于kafka自身。OffsetFetchRequest.CurrentVersion为1(默认)。
那么要实现一个消费者消费滞后预警,就要兼容两种方式,那么我们就详细的来介绍这两种方式的实现。
 
二,重要工具类
1,ConsumerOffsetChecker
Kafka提供的检查消费者消费偏移,LogEndSize和lagsize的工具。我们实现自己的监控均可以模仿该类实现。本文也仅限于基于该类将实现过程。
2,ZkUtils
Kafka提供的操作Zookeeper的工具类。
3,SimpleConsumer
Kafka消费者实现类。Kafka的副本同步,低级消费者,高级消费者都是基于该类实现从kafka消费消息的。
4,OffsetRequest
消费者去获取分区数据偏移的请求类,对应的请求key是:RequestKeys.OffsetsKey。在kafka的服务端kafkaApis的处理函数是:handleOffsetRequest(request)
5,OffsetFetchRequest
这个是请求某个topic的某个消费组的消费偏移,对应的请求key:RequestKeys.OffsetFetchKey。在kafka的服务端kafkaApis的处理函数是:handleOffsetFetchRequest(request)
6,OffsetManager
偏移管理器。内部维护了一个Scheduler,会定时执行compact,进行偏移的合并。
 
三,源代码实现
1,首先是获得消费者的消费偏移
ConsumerOffsetChecker当main方法中首先是获得topic列表
[Bash shell] 纯文本查看 复制代码
 

val topicList = topics match {
  case Some(x) => x.split(",").view.toList
  case None => ZkUtils.getChildren(zkClient, groupDirs.consumerGroupDir +  "/owners").toList
}

接着是建立到Broker链接,然后从kafka获取消费者偏移

[Bash shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
val topicPartitions = topicPidMap.flatMap { case(topic, partitionSeq) => partitionSeq.map(TopicAndPartition(topic, _)) }.toSeq
val channel = ClientUtils.channelToOffsetManager(group, zkClient, channelSocketTimeoutMs, channelRetryBackoffMs)
 
debug("Sending offset fetch request to coordinator %s:%d.".format(channel.host, channel.port))
channel.send(OffsetFetchRequest(group, topicPartitions))
val offsetFetchResponse = OffsetFetchResponse.readFrom(channel.receive().buffer)
debug("Received offset fetch response %s.".format(offsetFetchResponse))
 
offsetFetchResponse.requestInfo.foreach { case (topicAndPartition, offsetAndMetadata) =>
  if (offsetAndMetadata == OffsetMetadataAndError.NoOffset) {
    val topicDirs = new ZKGroupTopicDirs(group, topicAndPartition.topic)
    // this group may not have migrated off zookeeper for offsets storage (we don't expose the dual-commit option in this tool
    // (meaning the lag may be off until all the consumers in the group have the same setting for offsets storage)
    try {
      val offset = ZkUtils.readData(zkClient, topicDirs.consumerOffsetDir + "/%d".format(topicAndPartition.partition))._1.toLong
      offsetMap.put(topicAndPartition, offset)
    } catch {
      case z: ZkNoNodeException =>
        if(ZkUtils.pathExists(zkClient,topicDirs.consumerOffsetDir))
          offsetMap.put(topicAndPartition,-1)
        else
          throw z
    }
  }
  else if (offsetAndMetadata.error == ErrorMapping.NoError)
    offsetMap.put(topicAndPartition, offsetAndMetadata.offset)
  else {
    println("Could not fetch offset for %s due to %s.".format(topicAndPartition, ErrorMapping.exceptionFor(offsetAndMetadata.error)))
  }
}

假如,获得的偏移信息为空,那么就从Zookeeper获取消费者偏移。
解决获取topic的分区的最大偏移,实际思路是构建simpleConsumer,然后由其 去请求偏移,再跟获取的消费者偏移做差就得到消费者最大偏移。

[Bash shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
topicList.sorted.foreach {
  topic => processTopic(zkClient, group, topic)
}
topicPidMap.get(topic) match {
  case Some(pids) =>
    pids.sorted.foreach {
      pid => processPartition(zkClient, group, topic, pid)
    }
  case None => // ignore
}

在processPartition中

[Bash shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
val offsetOpt = offsetMap.get(topicPartition)
val groupDirs = new ZKGroupTopicDirs(group, topic)
val owner = ZkUtils.readDataMaybeNull(zkClient, groupDirs.consumerOwnerDir + "/%s".format(pid))._1
ZkUtils.getLeaderForPartition(zkClient, topic, pid) match {
  case Some(bid) =>
    val consumerOpt = consumerMap.getOrElseUpdate(bid, getConsumer(zkClient, bid))
    consumerOpt match {
      case Some(consumer) =>
        val topicAndPartition = TopicAndPartition(topic, pid)
        val request =
          OffsetRequest(immutable.Map(topicAndPartition -> PartitionOffsetRequestInfo(OffsetRequest.LatestTime, 1)))
        val logSize = consumer.getOffsetsBefore(request).partitionErrorAndOffsets(topicAndPartition).offsets.head

然后做差得到LagSize

[Bash shell] 纯文本查看 复制代码
1
2
3
val lagString = offsetOpt.map(o => if (o == -1) "unknown" else (logSize - o).toString)
println("%-15s %-30s %-3s %-15s %-15s %-15s %s".format(group, topic, pid, offsetOpt.getOrElse("unknown"), logSize, lagString.getOrElse("unknown"),
                                                       owner match {case Some(ownerStr) => ownerStr case None => "none"}))

getConsumer方法中

[Bash shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private def getConsumer(zkClient: ZkClient, bid: Int): Option[SimpleConsumer] = {
  try {
    ZkUtils.readDataMaybeNull(zkClient, ZkUtils.BrokerIdsPath + "/" + bid)._1 match {
      case Some(brokerInfoString) =>
        Json.parseFull(brokerInfoString) match {
          case Some(m) =>
            val brokerInfo = m.asInstanceOf[Map[String, Any]]
            val host = brokerInfo.get("host").get.asInstanceOf[String]
            val port = brokerInfo.get("port").get.asInstanceOf[Int]
            Some(new SimpleConsumer(host, port, 10000, 100000, "ConsumerOffsetChecker"))
          case None =>
            throw new BrokerNotAvailableException("Broker id %d does not exist".format(bid))
        }
      case None =>
        throw new BrokerNotAvailableException("Broker id %d does not exist".format(bid))
    }
  } catch {
    case t: Throwable =>
      println("Could not parse broker info due to " + t.getCause)
      None
  }
}

四,总结
该工具类的使用

[Bash shell] 纯文本查看 复制代码
1
bin/kafka-consumer-offset-checker.sh --group yourgroup -topic yourtopic --zookeeper  localhost:2181

输出结果

Offset是消费者消费到的偏移,logsize是kafka数据的最大偏移,Lag是二者的差。也即
LagSize = LogSize - Offset
得到我们消费组的滞后情况后,我们就可以根据需求(比如,设定滞后多少消息后给出告警),给出相应的告警。

转自:http://www.aboutyun.com/forum.php?mod=viewthread&tid=22215&extra=page%3D1&page=1&

Kafka详解六:Kafka如何通过源码实现监控的更多相关文章

  1. kafka详解(一)--kafka是什么及怎么用

    kafka是什么 在回答这个问题之前,我们需要先了解另一个东西--event streaming. 什么是event streaming 我觉得,event streaming 是一个动态的概念,它描 ...

  2. kafka详解(二)--kafka为什么快

    前言 Kafka 有多快呢?我们可以使用 OpenMessaging Benchmark Framework 测试框架方便地对 RocketMQ.Pulsar.Kafka.RabbitMQ 等消息系统 ...

  3. Linux下通过源码编译安装程序

    本文简单的记录了下,在linux下如何通过源码安装程序,以及相关的知识.(大神勿喷^_^) 一.程序的组成部分 Linux下程序大都是由以下几部分组成: 二进制文件:也就是可以运行的程序文件 库文件: ...

  4. 通过源码了解ASP.NET MVC 几种Filter的执行过程

    一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...

  5. 大数据入门第七天——MapReduce详解(二)切片源码浅析与自定义patition

    一.mapTask并行度的决定机制 1.概述 一个job的map阶段并行度由客户端在提交job时决定 而客户端对map阶段并行度的规划的基本逻辑为: 将待处理数据执行逻辑切片(即按照一个特定切片大小, ...

  6. 通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”

    通过源码了解ASP.NET MVC 几种Filter的执行过程   一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神 ...

  7. 在centos6.7通过源码安装python3.6.7报错“zipimport.ZipImportError: can't decompress data; zlib not available”

    在centos6.7通过源码安装python3.6.7报错: zipimport.ZipImportError: can't decompress data; zlib not available 从 ...

  8. 通过源码编译安装VIM

    开发中使用的是Ubuntu 12.04 LTS,通过sudo apt-get install vim安装的版本较低,不支持YCM,所以,用源码编译并安装最新的Vim. 卸载旧版本的Vim: sudo ...

  9. echarts 通过源码方法 传入对应data数据获取分割步长值

    通过源码方法获取这里的分割数字长度 /** * Quantity of a number. e.g. 0.1, 1, 10, 100 * * @param {number} val * @return ...

随机推荐

  1. 【转】开发者应该了解的API技术清单

    [转载贴] 作为一名开发者,诚然编写代码如同作家提笔挥毫,非常有成就感与乐趣,但同时我也觉得删除代码是件不相伯仲的美事.为什么呢?因为在进行删除工作 时,意味着自己找出了造成干扰的位置,意味着找到了冗 ...

  2. CodeIgniter框架——CI的执行流程

    应用程序流程图 CodeIgniter执行流程 源码分析——CI到底做了些什么 (由welcome的例子出发——讲解index.php——讲解CodeIgniter.php) (load_class的 ...

  3. svn 更新文件冲突,提示中文乱码解决

    问题描述: update 操作提示错误信息,中文乱码 和 “Please execute the 'Cleanup' command.” Cleanup 操作报错: 解决办法: 1. 工具下载(sql ...

  4. 《从零开始学Swift》学习笔记(Day 22)——闭包那些事儿!

    原创文章,欢迎转载.转载请注明:关东升的博客    我给Swift 中的闭包一个定义:闭包是自包含的匿名函数代码块,可以作为表达式.函数参数和函数返回值,闭包表达式的运算结果是一种函数类型. Swif ...

  5. Harmonic Value Description(构造题)

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission ...

  6. 数据类型比较:Long和BigDecimal

    1.基本类型: 基本类型可以用:">" "<" "==" 2.基本类型包装类:(对象类型) 2.1 Long 型: 要比较两个L ...

  7. php var_dump()函数的详解

    说明:var_dump()方法,判断一个变量的类型与长度,并输出变量的数值,如果变量有值,则输出是变量的值,并返回数据类型.显示关于一个或多个表达式的结构信息,包括表达式的类型与值.数组将递归展开值, ...

  8. hbase运行时ERROR:org.apache.hadoop.hbase.PleaseHoldException:Master is initializing的解决方法

    最终解决了,其实我心中有一句MMP. 版本: hadoop 2.6.4 + hbase0.98 第一个问题,端口问题8020 hadoop默认的namenode 资源子接口是8020 端口,然后我这接 ...

  9. iOS KVO详解

    一.KVO 是什么? KVO 是 Objective-C 对观察者设计模式的一种实现.[另外一种是:通知机制(notification),详情参考:iOS 趣谈设计模式——通知]: KVO 提供一种机 ...

  10. TFS二次开发-基线文件管理器(1)-设计

    CMMI在做基线文件管理的时候,常常是需要记录一部分基线文件的版本.并且这个基线文件记录也需要进行版本控制.TFS在做这件事的时候一般来说会选用标签(Lable)来做一系列文件的版本记录. 但是我发现 ...