1. 消息有序性
我们需要从2个方面看待消息有序性
- 第一,发送端能否保证发送到服务器的消息是有序的
- 第二,接收端能否有序的消费服务器中的数据
发送端一般通过同步发送实现,即一次仅发送一条,等返回成功后,再发送下一条,接收端一般仅通过一个消费者参与消费实现
2. 发送端消息有序性
2.1 Kafka如何保证单partition有序?
Kafka分布式的单位是partition,同一个partition用一个write ahead log组织,所以可以保证FIFO的顺序。不同partition之间不能保证顺序。但是绝大多数用户都可以通过message key来定义,因为同一个key的message可以保证只发送到同一个partition,比如说key是user id,table row id等等,所以同一个user或者同一个record的消息永远只会发送到同一个partition上,保证了同一个user或record的顺序。
也就说通过队列,保证partition上的数据元素是有序的。通过设置相同的路由,让多个数据被路由到同一个partition即可。
2.2 client消息发送原理
下面,我们要如何保证数据从client发送到服务器上的partition是有序的?
-
消息发送对于用户线程有同步和异步之分
同步能保证逐条发送消息,并保证是有序的
异步通常也是有序的,因为tcp长连接方式 按序读取InFlightRequests队列并发送至服务器上,tcp保证是有序的。但是如果开启了重试机制,可能会导致乱序。
-
后台的发送线程只有异步方式
异步场景下开启重试策略时:
max.in.flight.requests.per.connection=5 默认值,控制发送窗口的大小,即连续发送次数
如果把 retries 设为非零整数,同时把 max.in.flight.requests.per.connection 设为比 1 大的数,那么,如果第一个批次消息写入失败,而第二个批次写入成功,broker 会重试写入第一个批次。如果此时第一个批次也写入成功,那么两个批次的顺序就反过来了。
如果发送端配置了重试机制,kafka不会等之前那条消息完全发送成功才去发送下一条消息,这样可能会出现,发送了1,2,3条消息,第一条超时了,后面两条发送成功,再重试发送第1条消息,这时消息在broker端的顺序就是2,3,1了
一般来说,如果某些场景要求消息是有序的,那么消息是否写入成功也是很关键的,所以不建议把retries设为 0。可以把 max.in.flight.requests.per.connection 设为 1,这样在生产者尝试发送第一批消息时,就不会有其他的消息发送给broker。不过这样会严重影响生产者的吞吐量,所以只有在对消息的顺序有严格要求的情况下才能这么做。
3. 接收端消息有序性
kafka保证全链路消息顺序消费,需要从发送端开始,将所有有序消息发送到同一个分区,然后用一个消费者去消费,但是这种性能比较低,可以在消费者端接收到消息后将需要保证顺序消费的几条消费发到内存队列(可以搞多个),一个内存队列开启一个线程顺序处理消息。
仅有一个消费者访问一个分区,可以保证有序,但是效率低。当有多个消费者时,通过新增业务队列,让所有数据都发到业务队列中,排序后再进行业务处理。