如何保证kafka消费端接收到消息的有序性

kafka是一款高吞吐量的发布、订阅消息系统,在发送消息时,消息会被发送到对应的分区中,然后通过有序队列(ArrayDeque)来存储,所以如果要保证消息的顺序性,理论上只需要保证消息被发送到同一个分区即可。

简单画一下,帮助理解,正常情况下,假如我们有两个消费者客户端,订阅了某一个topic,这个topic有两个分区,那么kafka分配时就会让一个消费端对应一个分区,发送消息时,消息也会被均匀的发送到两个分区中。

多个消费端、多个分区
在这里插入图片描述

效果演示,新建一个topic:test_order_topic,设置3个分区。
在这里插入图片描述
按编号顺序发送10条消息

for (int i = 1; i <= 10; i++) {
    kafkaTemplate.send("test_order_topic", String.valueOf(i));
}

启动3个消费端,分别消费3个分区

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当然假设你只有一个消费端,多个分区,也是同样无法保证顺序的

在这里插入图片描述

2020-09-29 09:44:05.808  INFO 8 --- [ntainer#0-0-C-1] o.s.k.l.KafkaMessageListenerContainer    : partitions assigned: [test_order_topic-0, test_order_topic-1, test_order_topic-2]

在这里插入图片描述

通过多个分区与多个消费端相互对应这样的设计就能充分利用消费端的资源,使得消息可以并行消费,但也正是因为这个原因导致无法保证发送的消息与消费端消费到的消息顺序性。所以通过分析我们也能想到只要我们将需要保证顺序的消息固定发送一个分区中,自然就能保证顺序性了。

实现顺序性

1、直接指定分区

将消息发送到指定的分区编号:1中。

for (int i = 1; i <= 10; i++) {
    kafkaTemplate.send("test_order_topic", 1, "order_key", String.valueOf(i));
}

在这里插入图片描述

2、直接指定key

如果发送消息时,指定了key值,那么默认情况下就会根据key的hash值与分区数取模,决定消息应该发送到哪个分区中,所以指定key时也就间接的等于指定了要发送到哪个分区。

for (int i = 1; i <= 10; i++) {
    kafkaTemplate.send("test_order_topic", "order_key", String.valueOf(i));
}

在这里插入图片描述

源码部分

	//找到消息发送对应的分区
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        //获取主题中的分区数量
        int numPartitions = partitions.size();
        //如果发送消息时没有指定key值,则按照取模轮询的方式获取分区
        if (keyBytes == null) {
            int nextValue = nextValue(topic);
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                return availablePartitions.get(part).partition();
            } else {
                // no partitions are available, give a non-available partition
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else {
            // hash the keyBytes to choose a partition
  			//如果指定了key,则根据key的hash值与分区数取模,所以当指定了key值时,发送的分区将会固定。
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

同一个分区中的消息也有可能乱序

上述只是分析了正常的情况,实际上kafka中有一种处理异常重试方案,理论上也会影响消息的顺序性,哪怕你的消息在同一个分区中。

这里主要涉及到两个参数:retries、max.in.flight.requests.per.connection。

retries:生产者在发送消息时,如果遇到理论上重试就能成功的异常,会根据此值进行重试。
max.in.flight.requests.per.connection:在收到服务端消息确认发送之前,可以提前发送的消息数量。

结合两个参数产生乱序的场景分析

假设,第一批消息发送时,由于设置了max.in.flight.requests.per.connection参数,此时第二批消息(max.in.flight.requests.per.connection指定数量的消息)无须等服务端确认收到第一批消息时就可以提前发送,刚好第一批消息又发送异常并且也配置了允许重试,于是在第一批消息重试的过程中,第二批消息无异常正常发送出去了,随后第一批消息经过重试后也成功发送出去了,这样就造成了消息顺序被打乱了。

源码中对于这两个参数的含义也做了说明,并且说明中都提到了可能造成消息顺序被打乱的可能性。
在这里插入图片描述

如何避免

1、不允许重试,允许提前发送消息

由于不会重试,也就不会产生乱序问题,带来的问题就是可能会造成消息经常发送失败

2、允许重试,但是不允许提前发送消息

保证了正常情况下消息都能够被发送,但是不允许提前发送也就等于牺牲了发送消息的吞吐量。

总结

无论如何要做到消息的顺序性消费,就会造成资源的浪费与性能的降低,因为消息必须被放入一个分区中,就等于只能被一个消费端线程串行消费,降低了消费端的吞吐量,并且如果再考虑消息发送失败重试的场景,还会造成生产者的吞吐量也同时降低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值