kafka客户端实践及原理剖析

分区的作用:

负载均衡,实现系统的高伸缩性,不同的分区能够被放置到不同节点的机器上,而数据的读写操作也都是针对分区这个粒度而进行的,这样每个节点的机器都能独立地执行各自分区的读写请求处理

分区策略:

决定生产者将消息发送到哪个分区的算法

  • 默认策略:如果指定了 Key,那么默认实现按消息键保序策略;如果没有指定 Key,则使用轮询策略。
  • 自定义分区策略

1 轮询策略

优点:

有非常优秀的负载均衡表现,它总是能保证消息最大限度地被平均分配到所有分区上,故默认情况下它是最合理的分区策略,也是我们最常用的分区策略之一。

2 随机策略,但是追求数据的均匀分布,还是使用轮询策略比较好

3 按key-ordering策略:

kafka允许为每条消息定义消息键,简称为key,一旦消息被定义了key,那么久可以保证同一个key的所有的消息都进入到相同的分区里,由于每个分区的消息处理都是有顺序的,所以这个策略称为按消息键保序策略

实现方式:

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); return Math.abs(key.hashCode()) % partitions.size();

适用场景:前后消息具有因果关系

4 基于地理位置分区策略

List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); return partitions.stream().filter(p -> isSouth(p.leader().host())).map(PartitionInfo::partition).findAny().get();

压缩算法

kafka的消息层次:1 消息集合 ;2 消息

一个消息集合中包含若干条日志项,而日志项才是真正封装消息的地方,kafka底层的消息日志由一系列消息集合日志项组成。kafka通常不会直接操作具体的一条条消息,它总是在消息集合这个层面上进行写入操作

kafka的消息格式:

V1版本:每条消息都需要执行CRC校验,但是CRC值是会发生变化的,比如Broker端可能会对消息时间戳字段进行更新,或者Broker端在执行消息格式转换时,都会更新CRC;压缩方式是把多条消息进行压缩然后保存到外层消息的消息体字段中

V2版本:消息的CRC校验工作被移到了消息集合这一层;压缩方式是对整个消息集合进行压缩

压缩时间点:生产者端和broker端

生产者和broker端都可以设置compression.type,broker端的compression.type默认值为producer,表示broker端会按照producer端的压缩方式压缩数据,当broker端设置的compression不是producer,并且和生产者端设置的值不一致时,broker端会解压再重压缩,此时broker端cpu使用率可能会飙升

当broker端为了兼容老版本的消费者程序时,会对新版本消息向老版本格式进行消息格式转换,这个过程会设计到消息的解压缩和重新压缩,还会让kafka丧失零拷贝的特性

Producer 端压缩、Broker 端保持、Consumer 端解压缩。

除了在 Consumer 端解压缩,Broker 端也会进行解压缩,但是这和前面提到消息格式转换时发生的解压缩是不同的场景。每个压缩过的消息集合在 Broker 端写入时都要发生解压缩操作,目的就是为了对消息执行各种验证。我们必须承认这种解压缩对 Broker 端性能是有一定影响的,特别是对 CPU 的使用率而言。

压缩算法对比:

吞吐量:LZ4 > Snappy > zstd 和 GZIP

压缩比:zstd > LZ4 > GZIP > Snappy

如何实现无消息丢失配置:

  1. 不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。记住,一定要使用带有回调通知的 send 方法。
  2. 设置 acks = all。acks 是 Producer 的一个参数,代表了你对“已提交”消息的定义。如果设置成 all,则表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。这是最高等级的“已提交”定义。

0 :表示只要消息发送出去,就直接任务消息发送成功

1 :表示只要Partition Leader接收到消息而且写入本地磁盘了,就认为成功了,默认配置

all :表示Partition Leader接收到消息之后,还必须要求ISR列表里跟Leader保持同步的那些Follower都要把消息同步过去,才能认为这条消息是写入成功了

  1. 设置 retries 为一个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到的 Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失。
  2. 设置 unclean.leader.election.enable = false。这是 Broker 端的参数,它控制的是哪些 Broker 有资格竞选分区的 Leader。如果一个 Broker 落后原先的 Leader 太多,那么它一旦成为新的 Leader,必然会造成消息的丢失
  3. 设置 replication.factor >= 3。这也是 Broker 端的参数。其实这里想表述的是,最好将消息多保存几份,毕竟目前防止消息丢失的主要机制就是冗余。
  4. 设置 min.insync.replicas > 1。这依然是 Broker 端参数,控制的是消息至少要被写入到多少个副本才算是“已提交”。设置成大于 1 可以提升消息持久性。在实际环境中千万不要使用默认值 1。
  5. 确保 replication.factor > min.insync.replicas。如果两者相等,那么只要有一个副本挂机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要在不降低可用性的基础上完成。推荐设置成 replication.factor = min.insync.replicas + 1。
  6. 确保消息消费完成再提交。Consumer 端有个参数 enable.auto.commit,最好把它设置成 false,并采用手动提交位移的方式。就像前面说的,这对于单 Consumer 多线程处理的场景而言是至关重要的。

kafka拦截器

生产者拦截器

允许在发送消息前,以及消息提交成功后,植入拦截器逻辑

实现方式:

//指定拦截器
Properties props = new Properties();
List<String> interceptors = new ArrayList<>();
interceptors.add("com.yourcompany.kafkaproject.interceptors.AddTimestampInterceptor"); // 拦截器1
interceptors.add("com.yourcompany.kafkaproject.interceptors.UpdateCounterInterceptor"); // 拦截器2
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);
……

// AddTimestampInterceptor和UpdateCounterInterceptor的实现,都要继承org.apache.kafka.clients.producer.ProducerInterceptor
// 实现核心方法:
// onSend:方法会在消息发送之前被调用
// onAcknowledgement:该方法会在消息成功提交或发送失败之后被调用
// 需要注意的是,这两个方法不是在同一个线程中被调用的,所以如果在两个方法中调用某个共享可变对象,一定要保证线程安全

消费者拦截器

支持在消费消息前,以及提交位移后编写特定逻辑

实现方式:

//指定拦截器,实现拦截器需要继承org.apache.kafka.clients.consumer.ConsumerInterceptor
//实现核心方法:
// onConsume:该方法在消息返回给 Consumer 程序之前调用
// onCommit:Consumer 在提交位移之后调用该方法

两种拦截器都支持链的方式,可以将一组拦截器串联成一个大的拦截器,kafka会按照添加顺序依次执行拦截器逻辑

典型适用场景:

客户端监控、端到端系统性能检测、消息审计

kafka与TCP连接

kafka生产者程序概览:
  1. 构造生产者对象所需的参数对象
  2. 利用第一步的参数对象,创建KafkaProducer对象实例
  3. 使用KafkaProducer的send方法发送消息
  4. 调用KafkaProducer 的 close 方法关闭生产者并释放各种系统资源。
生产者何时创建TCP连接:

1 在创建 KafkaProducer 实例时,生产者应用会在后台创建并启动一个名为 Sender 的线程,该 Sender 线程开始运行时首先会创建与 Broker 的连接(但是在对象构造器中启动线程可能会造成 this 指针的逃逸)

bootstrap.servers是生产者的核心参数之一,指定了producer启动时要连接的Broker地址,在实际使用过程中,并不建议把左右的broker都配置到bootstrap.servers中,通常指定3到4台足以,因为producer一旦连接到集群的任一台broker,就能拿到整个集群的broker信息

2 当 Producer 更新了集群的元数据信息之后,如果发现与某些 Broker 当前没有连接,那么它就会创建一个 TCP 连接。同样地,当要发送消息时,Producer 发现尚不存在与目标 Broker 的连接,也会创建一个。

Producer 更新集群的元数据的场景:

  • 当 Producer 尝试给一个不存在的主题发送消息时,Broker 会告诉 Producer 说这个主题不存在。此时 Producer 会发送 METADATA 请求给 Kafka 集群,去尝试获取最新的元数据信息
  • Producer 通过 metadata.max.age.ms 参数定期地去更新元数据信息。该参数的默认值是 300000,即 5 分钟,也就是说不管集群那边是否有变化,Producer 每 5 分钟都会强制刷新一次元数据以保证它是最及时的数据。
生产者何时关闭TCP连接:

1 用户主动关闭

kill -9 , producer.close()

2 kafka自动关闭

与 Producer 端参数 connections.max.idle.ms 的值有关,默认是9分钟,如果9分钟内没有任何请求流过某个TCP连接,kafka会主动把该TCP连接关闭,当设置成-1时,kafka将不再干涉,但是Kafka 创建的这些 Socket 连接都开启了 keepalive,因此 keepalive 探活机制还是会遵守的。

注意:在第二种方式中,TCP 连接是在 Broker 端被关闭的,但其实这个 TCP 连接的发起方是客户端,因此在 TCP 看来,这属于被动关闭的场景,即 passive close。被动关闭的后果就是会产生大量的 CLOSE_WAIT 连接,因此 Producer 端或 Client 端没有机会显式地观测到此连接已被中断。

消费者何时创建TCP连接:

消费者端主要的程序入口是 KafkaConsumer 类。和生产者不同的是,构建 KafkaConsumer 实例时是不会创建任何 TCP 连接的,TCP 连接是在调用 KafkaConsumer.poll 方法时被创建的

在调用poll函数时有三个时机可以创建TCP连接:

  • 发送FindCoordinator 请求时。

消费者会发送FindCoordinator 请求给当前负载最小的broker,希望 Kafka 集群告诉它哪个 Broker 是管理它的协调者。

  • 连接协调者时:

上一步会反正真正的协调者,消费者知晓了真正的协调者后,会创建连向该 Broker 的 Socket 连接。只有成功连入协调者,协调者才能开启正常的组协调操作,比如加入组、等待组分配方案、心跳请求处理、位移获取、位移提交等。

  • 消费数据时

消费者会为每个要消费的分区创建与该分区领导者副本所在 Broker 连接的 TCP,消费者会为每个要消费的分区创建与该分区领导者副本所在 Broker 连接的 TCP

创建多少个TCP连接:
  1. 消费者对kafka集群一无所知,发送id=-1的请求,确定协调者和获取集群元数据
  2. 通过第一步返回的协调者所在broker id,发送id=Integer.MAX_VALUE 减去协调者所在 Broker 的真实 ID的请求,连接协调者,令其执行组成员管理操作。
  3. 使用真实的broker id,跟分区所在broker创建连接,执行实际的消息获取。
消费者何时关闭TCP连接:

1 用户主动关闭

手动调用 KafkaConsumer.close() 方法,手动kill

2 kafka自动关闭

由消费者端参数 connection.max.idle.ms 控制,默认配置是9分钟,当9分钟之内,这个sorket连接上没有任何请求,那么消费者就会强行杀掉这个socket连接,但是跟生产者不同的是,消费者在编写程序时,可能会循环调用poll 方法消费消息,那么上面提到的所有请求都会被定期发送到 Broker,因此这些 Socket 连接上总是能保证有请求在发送,从而也就实现了“长连接”的效果

注意:当第三类 TCP 连接成功创建后,消费者程序就会废弃第一类 TCP 连接,之后在定期请求元数据时,它会改为使用第三类 TCP 连接。也就是说,最终你会发现,第一类 TCP 连接会在后台被默默地关闭掉。对一个运行了一段时间的消费者程序来说,只会有后面两类 TCP 连接存在。

Kafka 消息交付可靠性保障以及精确处理一次语义的实现

只有 Broker 成功“提交”消息且 Producer 接到 Broker 的应答才会认为该消息成功发送。不过倘若消息成功“提交”,但 Broker 的应答没有成功发送回 Producer 端(比如网络出现瞬时抖动),那么 Producer 就无法确定消息是否真的提交成功了。因此,它只能选择重试,也就是再次发送相同的消息。

kafka如何做到既不丢失数据,也不重复发送消息?

幂等性和事务

幂等性:
  • 在命令式编程语言(比如 C)中,若一个子程序是幂等的,那它必然不能修改系统状态。这样不管运行这个子程序多少次,与该子程序关联的那部分系统状态保持不变。
  • 在函数式编程语言(比如 Scala 或 Haskell)中,很多纯函数(pure function)天然就是幂等的,它们不执行任何的 side effect。

优势:我们可以安全地重试任何幂等性操作,不会破坏我们的系统状态

幂等性producer:

在producer端设置:props.put(“enable.idempotence”, ture),或 props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true)。

kafka会自动做消息去重,底层原理,就是经典的用空间换时间的优化思路,即在broker端多保存一些字段,当producer发送了具有相同字段值的消息后,broker能够知道这些消息已经重复了。

但是作用范围有限:

1 只能保证某个主题的一个分区不出现重复消息

2 只能实现单会话上的幂等性

事务:

ACID,即原子性(Atomicity)、一致性 (Consistency)、隔离性 (Isolation) 和持久性 (Durability)。

事务型Producer能够保证将消息原子性写入到多个分区中,这批消息要么全部写入成功,要么全部失败

作用范围:跨分区,跨会话,但是性能相对来说差一点

设置事务型Producer的方式:

  • 和幂等性 Producer 一样,开启 enable.idempotence = true。
  • 设置 Producer 端参数 transactional. id。最好为其设置一个有意义的名字。

producer.initTransactions(); try { producer.beginTransaction(); producer.send(record1); producer.send(record2); producer.commitTransaction(); } catch (KafkaException e) { producer.abortTransaction(); }

comsumer端:设置 isolation.level 参数的值即可,两个取值:

  • read_uncommitted:这是默认值,表明 Consumer 能够读取到 Kafka 写入的任何消息,不论事务型 Producer 提交事务还是终止事务,其写入的消息都可以读取。很显然,如果你用了事务型 Producer,那么对应的 Consumer 就不要使用这个值。
  • read_committed:表明 Consumer 只会读取事务型 Producer 成功提交事务写入的消息。当然了,它也能看到非事务型 Producer 写入的所有消息。

消费组

  • Consumer Group 下可以有一个或多个 Consumer 实例。这里的实例可以是一个单独的进程,也可以是同一进程下的线程。
  • Group ID 是一个字符串,在一个 Kafka 集群中,它标识唯一的一个 Consumer Group。
  • Consumer Group 下所有实例订阅的主题的单个分区,只能分配给组内的某个 Consumer 实例消费。这个分区当然也可以被其他的 Group 消费。

理想情况下:consumer实例的数量应该等于该group订阅主题的分区总数

在实际使用过程中一般不推荐设置大于总分区数的 Consumer 实例。设置多余的实例只会浪费资源,而没有任何好处。

Kafka 仅仅使用 Consumer Group 这一种机制,却同时实现了传统消息引擎系统的两大模型:

  • 如果所有实例都属于同一个 Group,那么它实现的就是消息队列模型;
  • 如果所有实例分别属于不同的 Group,那么它实现的就是发布 / 订阅模型。

位移(offset):消费者在消费的过程中需要记录自己消费了多少数据,即消费位置信息

offset是一个KV对,key是分区,V对应Consumer消费该分区的最新位移,

历史:一开始kafka是将Consumer offset保存在zookeeper中的,但后面发现,zk这类元框架并不适合频繁的写更新,而consumer group的位移更新却是一个非常频繁的操作,这种大吞吐量的写操作会极大的拖慢zk的性能,所以在新版本中,,Kafka 社区重新设计了 Consumer Group 的位移管理方式,采用了将位移保存在 Kafka 内部主题的方法。这个内部主题就是让人既爱又恨的 __consumer_offsets

消费组的Rebalance:Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 Consumer 如何达成一致,来分配订阅 Topic 的每个分区。比如某个 Group 下有 20 个 Consumer 实例,它订阅了一个具有 100 个分区的 Topic。正常情况下,Kafka 平均会为每个 Consumer 分配 5 个分区。这个分配的过程就叫 Rebalance。

Rebalance的触发条件

  • 组成员数发生变更。比如有新的 Consumer 实例加入组或者离开组,抑或是有 Consumer 实例崩溃被“踢出”组。
  • 订阅主题数发生变更。Consumer Group 可以使用正则表达式的方式订阅主题,比如 consumer.subscribe(Pattern.compile("t.*c")) 就表明该 Group 订阅所有以字母 t 开头、字母 c 结尾的主题。在 Consumer Group 的运行过程中,你新创建了一个满足这样条件的主题,那么该 Group 就会发生 Rebalance。
  • 订阅主题的分区数发生变更。Kafka 当前只能允许增加一个主题的分区数。当分区数增加时,就会触发订阅该主题的所有 Group 开启 Rebalance。

在 Rebalance 过程中,所有 Consumer 实例都会停止消费,等待 Rebalance 完成

目前 Rebalance 的设计是所有 Consumer 实例共同参与,全部重新分配所有分区。其实更高效的做法是尽量减少分配方案的变动。例如实例 A 之前负责消费分区 1、2、3,那么 Rebalance 之后,如果可能的话,最好还是让实例 A 继续消费分区 1、2、3,而不是被重新分配其他的分区。这样的话,实例 A 连接这些分区所在 Broker 的 TCP 连接就可以继续用,不用重新创建连接其他 Broker 的 Socket 资源。

位移主题__consumer_offsets

需求要求:实现高持久性,还有支持频繁的写操作

原理:将consumer的位移数据作为一条条普通的kafka消息,提交到__consumer_offset中,__consumer_offset的主要作用是保存kafka消费者的位移信息,它的消息格式是kafka自定义的,用户不能修改,也不能随意向这个主题写消息,因为一旦写入的消息不满足kafka的规定的格式,那么kafka内部无法成功解析,就会造成broker的崩溃

位移主题的消息格式

  • 使用kv格式,key保存,v保存位移值还有位移提交的一些其他的元数据
  • 用于保存Consumer group信息的消息,主要用来注册Consumer Group
  • 用于删除Group过期位移,甚至是删除Group的消息,专属名字,tombstone消息即墓碑消息,也成为delete mark,一旦某个Consumer group下的所有的consumer实例都停止了,而且他们的位移数据都已被删除时,kafka会向位移主题的对应分区写入tombstone消息,表明要彻底删除这个group的信息

位移主题什么时候创建?

当 Kafka 集群中的第一个 Consumer 程序启动时,Kafka 会自动创建位移主题,位移注意的分区数通过offsets.topic.num.partitions来设置,默认值是50,副本数通过offsets.topic.replication.factor控制,默认值是3

Consumer位移提交:自动提交和手动提交

enable.auto.commit:true 自动定期提交,提交间隔由auto.commit.interval.ms控制;false 手动提交,kafka Consumer API提供consumer.commitSync提交位移

位移主题中的过期消息怎么删除?

kafka使用Compact策略删除位移主题中的过期消息,避免该主题无期限膨胀

对于同一个key的两条消息M1和M2,如果M1的发送时间早于M2,那么M1是过期消息,compact的过程就是扫描日志的所有消息,剔除那些过期的消息,然后把剩下的消息整理在一起

kafka提供了专门的后台线程定期巡检待compact的主题,这个后台线程叫Log Cleaner ( 可以用 jstack 命令查看一下 kafka-log-cleaner-thread 前缀的线程状态)

组合使用异步提交和同步提交

   try {
           while(true) {
                        ConsumerRecords<String, String> records = 
                                    consumer.poll(Duration.ofSeconds(1));
                        process(records); // 处理消息
                        commitAysnc(); // 使用异步提交规避阻塞
            }
} catch(Exception e) {
            handle(e); // 处理异常
} finally {
            try {
                        consumer.commitSync(); // 最后一次提交使用同步阻塞式提交
  } finally {
       consumer.close();
}
}

同时使用了 commitSync() 和 commitAsync()。对于常规性、阶段性的手动提交,我们调用 commitAsync() 避免程序阻塞,而在 Consumer 要关闭前,我们调用 commitSync() 方法执行同步阻塞式的位移提交,以确保 Consumer 关闭前能够保存正确的位移数据。将两者结合后,我们既实现了异步无阻塞式的位移管理,也确保了 Consumer 位移的正确性

协调者Coordinator

负责为Group执行Rebalance以及提供位移管理和组成员管理等

Consumer 端应用程序在提交位移时,其实是向 Coordinator 所在的 Broker 提交位移。同样地,当 Consumer 应用启动时,也是向 Coordinator 所在的 Broker 发送各种请求,然后由 Coordinator 负责执行消费者组的注册、成员管理记录等元数据管理操作。

所有broker都会创建和启动Coordinator,kafka为某个consumer group确定coordinator所在的broker的算法有2个步骤:

  1. 确定由位移主题的哪个分区来保存该group数据

partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)。

  1. 找出该分区Leader副本所在的broker,该broker即为对应的Coodinator

当consumer group出现问题,需要快速排查broker端日志时,我们也能根据这个算法准确定位coordinator对应的broker

消费组rebalance:

当consumer group完成rebalance之后,每个consumer实例都会定期向coordinator发送心跳请求,表明它存活,如果某个consumer不能及时发送心跳请求,coordinator就会认为该consumer已经死了,从而将其从group中移除,开始新的rebalance。

consumer端的:

session.timeout.ms默认值为10s,用来控制心跳间隔时间。

heartbeat.interval.ms:控制发送心跳请求频率,越小,实例发送心跳请求的频率越高

max.poll.interval.ms:限定了 Consumer 端应用程序两次调用 poll 方法的最大时间间隔。它的默认值是 5 分钟,表示你的 Consumer 程序如果在 5 分钟之内无法消费完 poll 方法返回的消息,那么 Consumer 会主动发起“离开组”的请求,Coordinator 也会开启新一轮 Rebalance。

max.poll.records:默认值500条,表明调用一次KafkaConsumer.poll 方法,最多返回 500 条消息,规定了单次 poll 方法能够返回的消息总数的上限

避免不必要的rebalance:

1 建议无脑设置:

  • 设置 session.timeout.ms = 6s。
  • 设置 heartbeat.interval.ms = 2s。
  • 要保证 Consumer 实例在被判定为“dead”之前,能够发送至少 3 轮的心跳请求,即 session.timeout.ms >= 3 * heartbeat.interval.ms。

2 根据业务合理设置max.poll.interval.ms,max.poll.records

3 检查consumer端的GC表现

应用中同时出现了设置相同 group.id 值的消费者组程序和独立消费者程序,那么当独立消费者程序手动提交位移时,Kafka 就会立即抛出 CommitFailedException 异常

多线程开发消费者实例:

  • 消费者程序启动多个线程,每个线程维护专属的 KafkaConsumer 实例,负责完整的消息获取、消息处理流程

public class KafkaConsumerRunner implements Runnable {
     private final AtomicBoolean closed = new AtomicBoolean(false);
     private final KafkaConsumer consumer;


     public void run() {
         try {
             consumer.subscribe(Arrays.asList("topic"));
             while (!closed.get()) {
      ConsumerRecords records = 
        consumer.poll(Duration.ofMillis(10000));
                 //  执行消息处理逻辑
             }
         } catch (WakeupException e) {
             // Ignore exception if closing
             if (!closed.get()) throw e;
         } finally {
             consumer.close();
         }
     }


     // Shutdown hook which can be called from a separate thread
     public void shutdown() {
         closed.set(true);
         consumer.wakeup();
     }
  • .消费者程序使用单或多线程获取消息,同时创建多个消费线程执行消息处理逻辑。处理消息则交由特定的线程池来做,从而实现消息获取与消息处理的真正解耦
private final KafkaConsumer<String, String> consumer;
private ExecutorService executors;
...


private int workerNum = ...;
executors = new ThreadPoolExecutor(
  workerNum, workerNum, 0L, TimeUnit.MILLISECONDS,
  new ArrayBlockingQueue<>(1000), 
  new ThreadPoolExecutor.CallerRunsPolicy());


...
while (true)  {
  ConsumerRecords<String, String> records = 
    consumer.poll(Duration.ofSeconds(1));
  for (final ConsumerRecord record : records) {
    executors.submit(new Worker(record));
  }
}
..

两种方案对比:

消费组的消费进度的监控

Lag 或 Consumer Lag:就是指消费者当前落后于生产者的程度

马太效应:由于消费者的速度无法匹及生产者的速度,极有可能导致它消费的数据已经不在操作系统的页缓存中了。这样的话,消费者就不得不从磁盘上读取它们,这就进一步拉大了与生产者的差距。

查看消费进度的方法:

  1. 使用kafka自带命令:

$ bin/kafka-consumer-groups.sh --bootstrap-server --describe --group

当遇到以下情况时,说明没有启用消费者进程,当前消费组没有activa的消费者实例

  1. Kafka Java Consumer API:

public static Map<TopicPartition, Long> lagOf(String groupID, String bootstrapServers) throws TimeoutException {
        Properties props = new Properties();
        props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        try (AdminClient client = AdminClient.create(props)) {
        //获取给定消费者组的最新消费消息的位移
            ListConsumerGroupOffsetsResult result = client.listConsumerGroupOffsets(groupID);
            try {       
                Map<TopicPartition, OffsetAndMetadata> consumedOffsets = result.partitionsToOffsetAndMetadata().get(10, TimeUnit.SECONDS);
                props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 禁止自动提交位移
                props.put(ConsumerConfig.GROUP_ID_CONFIG, groupID);
                props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
                props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
                try (final KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
                     //获取订阅分区的最新消息位移
                    Map<TopicPartition, Long> endOffsets = consumer.endOffsets(consumedOffsets.keySet());
                    //相减
                    return endOffsets.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(),
                            entry -> entry.getValue() - consumedOffsets.get(entry.getKey()).offset()));
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                // 处理中断异常
                // ...
                return Collections.emptyMap();
            } catch (ExecutionException e) {
                // 处理ExecutionException
                // ...
                return Collections.emptyMap();
            } catch (TimeoutException e) {
                throw new TimeoutException("Timed out when getting lag for consumer group " + groupID);
            }
        }
    }
  1. Kafka JMX 监控指标

lead:消费者消费的最新消息的位移与分区第一条消息的差值。lag越大,lead越小,当lead趋于0时,说明可能快要丢数据了,当此时消息被删除,一直会导致消费者程序重新调整位移值。‘

activeController:监控控制器的存活状态

  • 33
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1/kafka是一个分布式的消息缓存系统 2/kafka集群中的服务器都叫做broker 3/kafka有两类客户端,一类叫producer(消息生产者),一类叫做consumer(消息消费者),客户端和broker服务器之间采用tcp协议连接 4/kafka中不同业务系统的消息可以通过topic进行区分,而且每一个消息topic都会被分区,以分担消息读写的负载 5/每一个分区都可以有多个副本,以防止数据的丢失 6/某一个分区中的数据如果需要更新,都必须通过该分区所有副本中的leader来更新 7/消费者可以分组,比如有两个消费者组A和B,共同消费一个topic:order_info,A和B所消费的消息不会重复 比如 order_info 中有100个消息,每个消息有一个id,编号从0-99,那么,如果A组消费0-49号,B组就消费50-99号 8/消费者在具体消费某个topic中的消息时,可以指定起始偏移量 每个partition只能同一个group中的同一个consumer消费,但多个Consumer Group可同时消费同一个partition。 n个topic可以被n个Consumer Group消费,每个Consumer Group有多个Consumer消费同一个topic Topic在逻辑上可以被认为是一个queue,每条消费都必须指定它的Topic,可以简单理解为必须指明把这条消息放进哪个queue里。为了使得Kafka的吞吐率可以线性提高,物理上把Topic分成一个或多个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。若创建topic1和topic2两个topic,且分别有13个和19个分区 Kafka的设计理念之一就是同时提供离线处理和实时处理。根据这一特性,可以使用Storm这种实时流处理系统对消息进行实时在线处理,同时使用Hadoop这种批处理系统进行离线处理,还可以同时将数据实时备份到另一个数据中心,只需要保证这三个操作所使用的Consumer属于不同的Consumer Group即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值