1. Información general
Guía de preguntas:
1. Almacenamiento compensado del consumidor del grupo de consumidores de
Kafka , Kafka admite dos versiones 2. ¿Cuál es el papel de la clase ConsumerOffsetChecker?
3. ¿Cómo realiza Kafka el seguimiento a través del código fuente?
1. Introducción a las ideas básicas
Como cola de mensajes útil y ampliamente utilizada, Kafka es básicamente indispensable en los sistemas de procesamiento de big data. Por supuesto, como cola de mensajes para almacenar mensajes en caché, es extremadamente importante para nosotros monitorear su tráfico y alertar sobre el retraso de consumo.
Los hermanos y hermanas que hayan leído los artículos anteriores, <Kafka fuente de la serie de código fuente de análisis de código proceso de consumo SimpleConsumer> y <Kafka código fuente de la serie de consumidor avanzado análisis de rendimiento API> deben leer este artículo será muy simple. De hecho, utiliza SimpleConsumer para obtener el último desplazamiento de Partition y utiliza la herramienta de Zookeeper para obtener el desplazamiento de consumo de cada partición del grupo de consumidores, la diferencia entre los dos es lagSize.
Pero el almacenamiento de compensación de consumo real del grupo de consumidores de Kafka, Kafka admite dos versiones:
1,基于Zookeeper。OffsetFetchRequest.CurrentVersion为0。
2,基于kafka自身。OffsetFetchRequest.CurrentVersion为1(默认)。
Para darse cuenta de la advertencia de retraso de un consumidor, dos métodos deben ser compatibles, luego presentaremos la realización de estos dos métodos en detalle.
Segundo, herramientas importantes
1,ConsumerOffsetChecker
Kafka proporciona herramientas para verificar la compensación del consumo del consumidor, LogEndSize y lagsize. Podemos imitar este tipo de implementación cuando implementamos nuestro propio monitoreo. Este artículo también se limita al proceso de implementación basado en esta clase.
2 , ZkUtils
Kafka proporciona herramientas para operar Zookeeper.
3,SimpleConsumer
Clase de implementación de consumidor de Kafka. La sincronización de réplicas de Kafka, los consumidores de bajo nivel y los consumidores de alto nivel se basan en este tipo de implementación para consumir mensajes de Kafka.
4, OffsetRequest
El consumidor obtiene la clase de solicitud del desplazamiento de datos de la partición y la clave de solicitud correspondiente es: RequestKeys.OffsetsKey. La función de procesamiento de kafkaApis en el lado del servidor de kafka es: handleOffsetRequest (solicitud)
5, OffsetFetchRequest
Esta es la compensación de consumo de un determinado grupo de consumidores que solicita un determinado tema y la clave de solicitud correspondiente: RequestKeys.OffsetFetchKey. La función de procesamiento de kafkaApis en el lado del servidor de kafka es: handleOffsetFetchRequest (solicitud)
6 , OffsetManager
Administrador de compensaciones. Un planificador se mantiene internamente y compacto se ejecuta regularmente para fusionar compensaciones.
Tres, implementación del código fuente
1. El primero es obtener la compensación del consumidor
ConsumerOffsetChecker cuando el método principal es el primero en obtener la lista de temas.
val topicList = topics match {
case Some(x) => x.split(",").view.toList
case None => ZkUtils.getChildren(zkClient, groupDirs.consumerGroupDir + "/owners").toList
}
Lo siguiente es establecer un enlace a Broker y luego obtener la compensación del consumidor de kafka
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)))
}
}
Si la información de compensación obtenida está vacía, la compensación del consumidor se obtiene de Zookeeper.
Para resolver el desplazamiento máximo de la partición del tema, la idea real es construir un simpleConsumer, luego solicitar el desplazamiento y luego hacer una diferencia con el desplazamiento del consumidor obtenido para obtener el desplazamiento máximo del consumidor.
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
}
En proceso Partición
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
Luego haz la diferencia para obtener LagSize
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"}))
En el método getConsumer
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
}
}
Cuatro, resumen
Uso de esta herramienta
bin/kafka-consumer-offset-checker.sh --group yourgroup -topic yourtopic --zookeeper localhost:2181
Resultado de salida
La compensación es la compensación consumida por los consumidores, el tamaño de registro es la compensación máxima de los datos de Kafka y el Lag es la diferencia entre los dos. Es decir
LagSize = LogSize - Offset
Después de obtener la histéresis de nuestro grupo de consumidores, podemos dar las alarmas correspondientes según la demanda (por ejemplo, establecer el número de mensajes retrasados para dar una alarma).