点一下关注吧!!!非常感谢!!持续更新!!!
🚀 AI篇持续更新中!(长期更新)
AI炼丹日志-30-新发布【1T 万亿】参数量大模型!Kimi‑K2开源大模型解读与实践,持续打造实用AI工具指南!📐🤖
💻 Java篇正式开启!(300篇)
目前2025年08月04日更新到: Java-89 深入浅出 MySQL 搞懂 MySQL Undo/Redo Log,彻底掌握事务回滚与持久化 MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解
章节内容
上节我们完成了如下内容:
- 消费组测试,消费者变动对消费的影响
- 消费者的心跳机制
- 消费者的相关配置参数
主题和分区
Topic(主题)
Topic是Kafka中用于分类管理消息的逻辑单元,类似于关系型数据库中的数据库概念。每个Topic代表一类特定的消息流,例如:
- 用户行为日志可以创建"user_behavior"主题
- 订单交易可以创建"order_transaction"主题
- 系统监控指标可以创建"system_metrics"主题
Kafka中的主题名称需要保持唯一性,且命名应当清晰表达其用途。主题在创建时可以指定以下重要参数:
- 副本因子(replication factor):决定消息的冗余备份数量
- 保留时间(retention period):消息在Kafka中的存储时长
- 清理策略(cleanup policy):决定是压缩日志还是删除过期日志
Partition(分区)
Partition是Kafka中数据存储的基本物理单元,具有以下特点:
-
数据分散存储:
- 同一个Topic的数据会被分散存储在多个Partition中
- 例如一个包含100万条消息的Topic,如果分成10个Partition,则每个Partition大约存储10万条消息
-
分布特性:
- Partition可以分布在同一个Broker上
- 也可以分布在集群中的不同Broker上
- 这种设计使得Kafka能够实现水平扩展
-
分区策略:
- 推荐的分区数量是Broker数量的整数倍(如3个Broker,可设置3、6、9等分区数)
- 分区数量决定了Topic的最大并行消费能力
- 分区一旦创建后通常不能减少,但可以增加(需要谨慎规划)
-
优势:
- 提高并行处理能力
- 实现负载均衡
- 增强系统吞吐量
- 提高容错能力
Consumer Group(消费组)
Consumer Group是Kafka实现消息消费模型的逻辑概念:
-
消息模型:
- 单播模式:一条消息只被消费组中的一个消费者消费
- 广播模式:通过使用不同的消费组,实现消息的广播
-
消费保证:
- 保证消费组能够获取到特定主题的全部消息
- 在消费组内部,主题的每个分区只会被分配给一个消费者
- 例如:一个4分区的Topic,在一个3消费者的消费组中,会有一个消费者处理2个分区
-
再平衡机制:
- 当消费者加入或离开消费组时,Kafka会自动触发再平衡
- 确保分区在所有存活的消费者之间公平分配
Consumer(消费者)
Kafka消费者采用PULL模式从Broker读取数据:
-
PULL模式优势:
- 消费者可以自主控制消费速度
- 避免Broker推送过快导致消费者过载
- 消费者可以根据自身处理能力调节消费速率
-
消费控制:
- 消费者可以暂停和恢复消费
- 支持从特定偏移量开始消费
- 可以手动提交或自动提交消费位移
-
消费示例:
// 创建消费者配置
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test-group");
props.put("enable.auto.commit", "true");
// 创建消费者实例
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// 订阅主题
consumer.subscribe(Arrays.asList("my-topic"));
// 消费消息
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n",
record.offset(), record.key(), record.value());
}
}
- 消费配置:
fetch.min.bytes:控制每次fetch请求的最小数据量fetch.max.wait.ms:fetch请求最长等待时间max.poll.records:控制单次poll调用返回的最大记录数
反序列化
- Kafka的Broker中所有的消息都是字节数组,消费者获取到消息之后,需要先对消息进行反序列化处理,然后才能交由给用户程序消费。
- 消费者的反序列化器包括Key和Value。
自定义反序列化
如果要实现自定义的反序列化器,需要实现 Deserializer 接口:
public class UserDeserializer implements Deserializer<User> {
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
Deserializer.super.configure(configs, isKey);
}
@Override
public User deserialize(String topic, byte[] data) {
ByteBuffer buffer = ByteBuffer.allocate(data.length);
buffer.put(data);
buffer.flip();
int userId = buffer.getInt();
int usernameLen = buffer.getInt();
String username = new String(data, 8, usernameLen);
int passwordLen = buffer.getInt();
String password = new String(data, 8 + usernameLen, passwordLen);
int age = buffer.getInt();
User user = new User();
user.setUserId(userId);
user.setUsername(username);
user.setPassword(password);
user.setAge(age);
return user;
}
@Override
public User deserialize(String topic, Headers headers, byte[] data) {
return Deserializer.super.deserialize(topic, headers, data);
}
@Override
public void close() {
Deserializer.super.close();
}
}
消费者拦截器
消费者在拉取了分区消息之后,要首先经过反序列化器对Key和Value进行反序列化操作。 消费端定义消息拦截器,要实现 ConsumerInterceptor接口:
- 一个可插拔的接口,允许拦截、更改消费者接收到的消息,首要的用例在于将第三方组件引入消费者应用程序,用于定制监控、日志处理等
- 该接口的实现类通过configure方法获取消费者配置的属性,如果消费者配置中没有指定ClientID,还可以获取KafkaConsumer生成的ClientID,获取这个配置跟其他拦截器是共享的,需要保证不会在各个拦截器之间产生冲突。
- ConsumerInterceptor方法抛出异常会被捕获,但不会向下传播,如果配置了错误的参数类型,消费者不会抛出异常而是记录下来。
- ConsumerInterceptor回调发生在KafkaConsumer.poll()方法的同一个线程
public class ConsumerInterceptor01 implements ConsumerInterceptor<String, String> {
@Override
public ConsumerRecords<String, String> onConsume(ConsumerRecords<String, String> records) {
System.out.println("=== 消费者拦截器 01 onConsume ===");
return records;
}
@Override
public void onCommit(Map<TopicPartition, OffsetAndMetadata> offsets) {
System.out.println("=== 消费者拦截器 01 onCommit ===");
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
System.out.println("消费者设置的参数");
configs.forEach((k, v) -> {
System.out.println(k + ", " + v);
});
}
}
位移提交
相关概念
- Consumer 需要向Kafka记录自己的位移数据,这个汇报过程称为:提交位移(Committing Offsets)
- Consumer 需要为分配给它的每个分区提交各自的位移数据
- 位移提交的由Consumer端负责的,Kafka只负责保管,存到 __consumer_offsets 中
- 位移提交:自动提交和手动提交
- 位移提交:同步提交和异步提交
自动提交
Kafka Consumer后台提交
- 开启自动提交 enable.auto.commit=true
- 配置启动提交间隔:auto.commit.interval.ms,默认是5秒
位移顺序
自动提交位移的顺序:
- 配置 enable.auto.commit=true
- Kafka会保证在开始调用poll方法时,提交上次poll返回的所有消息的
- 因此自动提交不会出现消息丢失,但是会重复消费
重复消费
重复消费的场景:
- Consumer设置5秒提交offset
- 假设提交offset后3秒发生了Rebalance
- Rebalance之后所有的Consumer从上一次提交的Offset的地方继续消费
- 因为Rebalance发生前3秒的内的提交就丢失了
异步提交
- 使用 KafkaConsumer#commitSync,会提交所有poll返回的最新Offset
- 该方法为同步操作 等待直到 offset 被成功提交才返回
- 手动同步提交可以控制offset提交的时机和频率
位移管理
Kafka中,消费者根据消息的位移顺序消费消息,消费者的位移由消费者者管理,Kafka提供了消费者的API,让消费者自行管理位移。
重平衡
重平衡可以说是Kafka中诟病最厉害的一部分。 重平衡是一个协议,它规定了如何让消费者组下的所有消费者来分配Topic中每一个分区。 比如一个Topic中有100个分区,一个消费组内有20个消费者,在协调者的控制下可以让每一个消费者能分配到5个分区,这个分配过程就是重平衡。
重平衡的出发条件主要有三个:
- 消费者组内成员发生变更,这个变更包括了增加和减少消费者,比如消费者宕机退出消费组。
- 主题的分区数发生变化,Kafka目前只能增加分区数,当增加的时候就会触发重平衡
- 订阅的主题发生变化,当消费组使用正则表达式订阅主题,而恰好又新建了对应的主题,就会重平衡
为什么说重平衡让人诟病呢?因为重平衡过程中,消费者无法从Kafka消费消息,对Kafka的TPS影响极大,而如果Kafka集群内节点较多,比如数百个,重平衡耗时会很久。
避免Kafka消费者重平衡的最佳实践
在Kafka分布式系统中,完全避免消费者重平衡虽然难以实现,但通过合理配置可以有效减少不必要的重平衡发生。重平衡最主要的原因是消费者组协调器误判消费者状态,通常由以下几个关键配置参数控制:
核心参数解析
-
session.timeout.ms(会话超时时间)
- 定义:消费者与协调器断开连接多久后会被认为失效
- 典型场景:当网络出现临时故障或消费者处理消息时间过长时
- 默认值:10秒(Kafka 2.3+版本)
- 调优建议:在稳定的网络环境下可适当缩短,但需配合心跳间隔调整
-
heartbeat.interval.ms(心跳间隔)
- 定义:消费者发送心跳给协调者的频率
- 重要性:越频繁越不容易误判,但会增加网络开销
- 默认值:3秒
- 示例:如果设为1秒,协调器3次收不到心跳(session.timeout.ms=3秒)才会判定消费者失效
-
max.poll.interval.ms(最大轮询间隔)
- 定义:两次poll()调用之间的最大允许间隔
- 触发场景:当消息处理逻辑复杂或耗时较长时
- 默认值:5分钟
- 特殊场景:对于批量处理或复杂计算的消费者需要特别关注
推荐参数配置组合
针对不同业务场景,推荐以下配置策略:
常规消息处理场景:
session.timeout.ms=6000 # 6秒超时
heartbeat.interval.ms=2000 # 每2秒一次心跳
max.poll.interval.ms=300000 # 5分钟(默认值)
长耗时处理场景:
session.timeout.ms=10000 # 10秒超时
heartbeat.interval.ms=3000 # 每3秒一次心跳
max.poll.interval.ms=处理最长时间+60000 # 如预计最长处理2分钟,则设为180000
配置注意事项
-
心跳间隔与会话超时关系:
- 确保
session.timeout.ms ≥ 3 × heartbeat.interval.ms - 这样在网络抖动时能容忍至少2次心跳丢失
- 确保
-
监控与调优:
- 监控消费者组重平衡频率(可通过JMX指标)
- 对于频繁重平衡,可逐步调整timeout值并观察效果
-
特殊场景处理:
- 批量消费场景:适当增大max.poll.interval.ms
- 不稳定网络环境:增大session.timeout.ms
- 高吞吐场景:降低heartbeat.interval.ms但需评估资源消耗
通过合理配置这些参数,可以在保证消费者组稳定性的同时,最大限度地减少不必要的重平衡操作,提高系统整体吞吐量。