关键点总结
-
Kafka offset 顺序提交语义
- Kafka 的 offset 是按分区线性递增的,提交的 offset 表示“之前所有消息都已消费完成”。
- 无法跳过中间未提交的 offset,比如 offset=100 未提交,提交 offset=101 和以后无效。
假如分区内消息 offset 为:
100, 101, 102, 103...
- 场景一(合法提交):
- 先提交 offset=101 表示已消费完 offset 100 消息。
- 之后才能提交 102 表示已消费到 offset=101。
- 场景二(非法跳跃提交):
- offset=100 未被提交,消费者尝试提交 offset=101。
- Kafka 会忽略该提交,因为它与已有提交不连续,
- 或者新的提交会被视为无效,消费进度不会推进。
- 消费者依然会收到 offset=100 的消息,导致重复消费。
-
消费失败导致重试原因
- 消费时,失败的消息没有调用
MarkMessage标记已消费,导致 offset 始终停留在该消息。 - Kafka 重启或重新平衡时,会从这个未提交 offset 开始重复投递,形成死循环。
- 消费时,失败的消息没有调用
-
如何避免失败消息无限重试
- 失败时也调用
MarkMessage并提交 offset,表示跳过该消息; - 或基于业务逻辑维护重试次数,超过阈值才跳过;
- 可使用死信队列处理不合格消息。
- 失败时也调用
-
Sarama 中的提交调用建议
- 通常只调用
MarkMessage,由 Sarama 自动异步提交 offset; - 如果需要强制提交 offset 可调用
Commit(); - 不要单独提交未标记消息的 offset。
- 通常只调用
示例代码(带失败跳过的简化示范)
import "github.com/IBM/sarama"
for {
select {
case <-p.stop:
logger.Infow(ctx, tag, "msg", "run exit")
return
case msg, ok := <-p.messages:
if !ok {
logger.Infow(ctx, tag, "msg", "chan has closed")
return
}
err := p.handlerMessage(msg.ctx, msg.message)
if err != nil {
// 处理失败,打印日志,跳过该消息,避免死循环重试
logger.Errorw(msg.ctx, tag, "msg", "handlerMessage fail", "err", err,
"topic", msg.message.Topic, "partition", msg.message.Partition, "offset", msg.message.Offset)
// 标记该消息已消费(跳过)
msg.session.MarkMessage(msg.message, "")
// 如果需要立即提交,可以调用 Commit(通常不需要)
// msg.session.Commit()
continue
}
// 处理成功,标记消息
msg.session.MarkMessage(msg.message, "")
// 如果想手动提交 offset,如关闭时或定时提交
// msg.session.Commit()
}
}
说明
- 失败调用
MarkMessage是告诉 Kafka 可以提交该 offset,跳过消息,避免重试死循环; - 如果你想实现重试次数控制,需要额外设计计数逻辑(如 Redis),超过次数调用
MarkMessage跳过; - 建议利用 Kafka 的死信队列(DLQ)机制,将超过重试次数的消息发送到专门的主题,供后续排查;
Commit()可根据需要调用,Sarama 默认会自动批量提交已经标记的 offset。
需要我帮你设计重试计数和死信队列逻辑,或者示范更多细节代码,随时告诉我!