kafka-msg-delivery

77 阅读4分钟

摘要

其实消息交付语义 并不是kafka独有的,而是每一个“消息系统”都会面临的问题,业界抽象出三种语义

  • 最多一次(at most once):某个消息最多被消费一次
  • 最少一次(at least once):某个消息最少被消费一次
  • 精准一次(exactly once):某个消息被消费且仅被消费一次

其实很容易做出取舍,最好的肯定是精准一次,其次是最少一次,因为收到多次肯定比一次都收不到强,因为收到多次,消费端还可以可以根据消息内容进行幂等处理进行兼容。而最多一次,基本不会采用,因为会丢消息

Kafka 目前有很多版本,默认的消息交付语义是最少一次,而在0.11.0.0 版本之后支持了精准一次,本文主要介绍了Kafka精准一次的实现思路

如有问题,欢迎gitee 提 issue,共同进步

精准一次

其实摘要里的精准一次的定义是狭义的,并不完全。因为只从Consumer定义了精准消费一次,Producer同样面临精准一次的问题。

Producer 侧

在Kafka默认策略里,为了保证消息不丢失,Producer 没有收到Broker返回的消息接收成功通知时,会重新发送消息,这对Producer和Broker来说,意味着此时的交付语义是至少一次,无法满足精准一次的定义,即消息投递且仅被投递一次,针对这个问题,kafka从0.11.0.0 开始,每个Producer 都被分配一个,同时每个producer 生产的每条消息都会有一个序列号,在Kafka 内部会根据这个对消息进行去重,这样每个消息即使被投递多次,也只会被存储一次

这样的话,你也许会问,那Producer 并不是发送一次啊,还是可能发送多次。是的,遇到网络抖动导致无法收到Broker返回时,确实是会重复发送,但是重点是不会重复存储,也就不会出现重复的消息,间接的实现了精准一次。

另外Kafka额外有事务型的操作,假设同时发个多个Topic的消息,可以做到要么都发送成功,要么都发送不成功。但是需要注意的事,这类操作会更加的耗时,如果需要及时响应的服务,不建议这么使用

Consumer侧

消费侧有两种策略

  1. 先记录消费进度,再执行真正的消费逻辑。这样的风险在于,消费进度记录了,但是执行消费逻辑之前挂了,服务重启之后,根据消费进度,会跳过处理之前消费失败的消息,也就会导致消息丢失,属于至多消费一次
  2. 先消费,再记录进度。风险在于消费完了,没来的及记录进度。服务重启之后,查询进度,会重新再消费一次,属于至少消费一次

kafka 是怎么实现精准消费一次的呢?

分成两种情况

  1. 消费的逻辑是把TopicA的消息a转换成TopicB的消息b重新发给Kafka,这种情况下,TopicA的消费进度会附在TopicB的消息内,利用Producer 的事务发送机制,一旦发送异常,则不更新或者回滚TopicA 的消费进度,这样的话,消息a下次还会被消费。同时消息b也不一定会被消费(需要设置“隔离级别”)。消费者默认的隔离级别是读未提交,只要有消息就可以被看到,需要调整成读已提交,这样就只能看到事务提交之后的消息了
  2. 消费的逻辑依赖外部系统。这种情况下需要实现两个逻辑来保证业务的正确性
    1. 保证消息消费的幂等性
    2. 把消费进度的提交和业务处理在业务系统内捆绑成一个事务,保证消费进度提交失败时回滚事务

其实本质上来说,都是选择了先消费,再记录。只是增加了记录失败时回滚消费逻辑的能力。

总结

这里贴一段对kafka官方的翻译吧

kafka 自身在Kafka-Stream 支持精准一次的消费,同时事务性的Producer 和 Consumer 时可用的当处理Kafka内部流转消息在不同Topic。精准一次对于其他系统来说往往需要进行协同开发,但是Kafka提供了offset机制使得这些开发更加容易(参考Kafka-Stream)。其他情况下,Kafka默认提供的是至少消费一次的语义,当然,也支持使用者禁止Producer重试 和调整提交消费进度的逻辑来实现至多消费一次的语义

参考链接

Kafka-消息交付语义