关于 Kafka 的一点思考

1. Kafka 消费者可能遇到的问题

Kafka 消息队列在消费时可能遇到两个问题:

  1. 消息重复(Producer 设置 acksall 可能会有重复数据)
  2. 消息乱序(网络延迟)

但是这两者在处理的时候却必须综合考虑,如果消息的生产者按顺序生产消息,消费者在消费的时候也会期望消息的有序性,但是如果消息发生了重复或者乱序,消费者是无法区分的,也就无法针对两种情况分别进行处理。

因此我们必须通过某些手段来区分和解决这些问题。

2. 解决思路

针对的业务场景不同,我们能够使用的手段也不尽相同,总体来说:

  1. 唯一标示
  2. 状态机机制
  3. 幂等的方法

2.1 唯一标识

生产者在生产消息的时候为每一条消息增加一个递增的、唯一的标识。消费者在消费之前首先查询数据库该消息是否已经消费,如果已经消费了则直接忽略。该手段用来处理重复的数据简单直接。

在唯一标识生成的时候还可以附加生成规则,比如 唯一标识 = 批次号 + 版本序号。对消息顺序有严格要求的场景,可以根据同一批次对消息进行缓存,等同一批次内的版本序号都齐了再进行处理。

2.2 状态机机制1

状态机机制针对于有严格状态流转的业务场景,比如工作流程的处理、资源状态的转换等等。业务流程本身可以表示成一个状态机。那么在生产数据的时候可以把目标实体的当前状态和目标状态写入消息中,消费的过程中通过消息的当前状态和实体实际的当前状态进行对比,如果一致再进行处理。

如果不一致可以通知上游重发,也可以针对业务场景设置不同策略。

2.3 幂等的方法2

幂等性:

HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。同一个请求,发送一次和发送N次效果是一样的。

幂等性所表达的概念关注的是数学层面的运算和数值,并没有提及到数值的安全性问题。所以 RESTful 设计中将幂等性和安全性是作为两个不同的指标来衡量,如 POSTPUTGETDELETE 操作:

重要方法 安全 幂等
GET
DELETE
PUT
POST

因此,幂等性是系统的接口对外一种承诺(而不是实现),承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的接口会认为外部调用失败是常态,并且失败之后必然会有重试。

对于消息队列的消费者也是类似的。HTTP 方法中相对于 POST 方法,PUT 方法是幂等的。从资源状态转换的角度来考虑或者从容错的角度出发,在某些应用场景下将处理流程进行改造。

既然 POST 方法意味着创建资源的实例,那么我们可以将 POST 方法尽量改造成 PUT 方法。比如:如果消费者从消息队列中读取消息来进行资源的创建,我们可以给每条消息增加唯一标识,创建资源的时候如果该唯一标识不存在则直接创建资源,否则进行全量的更新(类似 MySQLon duplicate key update 子句或者 Replace 语法)。ElasticSearch 可以通过 _index_type_id 组合确定唯一一条文档,如果在写入数据的时候三者重复了,那么ElasticSearch 会用新的文档完全覆盖老的文档。这也是通过 KafkaElasticSearch 中导入数据时避免重复数据的一个技巧。

其实不仅幂等的方法满足幂等性,唯一标识的方案、状态机机制同样满足了幂等的要求。这三种方案可以进行任意地组合以应对复杂的业务场景。

参考文章

  1. 消息队列设计精要
  2. 分布式系统接口幂等性
  3. 幂等性 个人理解及应用