关于时序数据流经Kafka之后可能产生乱序的原因和解决方法

博主最近在做数据迁移的工作,但是在迁移的过程中遇到了一个问题,数据总是无缘无故的丢失,而且我的日志也没有报任何的错误异常信息,后经过排查,flink在消费kafka的时候我是通过事件时间处理数据的,有水位线的概念,由于kafka中的数据有大量的乱序现象,而且乱序的时间也比较严重,虽然写入数据的工作不是博主做的,但是抱着求知的心态,还是差了一下是什么原因会导致kafka中的数据出现乱序。

kafka简介:Kafka 作为一个流行的消息队列,以分布式高性能,高可靠性等特点已经在多种场景下广泛使用。

但在实际部署过程中,可能会因为配置原因导致经过 Kafka 的数据在接收方产生乱序,给后续处理环节带来排序等工作,造成不必要的处理开销,降低系统的处理性能和额外排序的工作。

其实可以通过合理的规划设计 Kafka 的配置和方法来避免消息在通过 Kafka 后乱序的产生,只需要遵循以下原则即可:对于需要确保顺序的一条消息流,发送到同一个 partition 上去。

Kafka 可以在一个 topic 下设置多个 partition 来实现分布式和负载均衡,由同一 consumer group 下的不同 consumer 去消费;这样的机制能够支持多线程分布式的处理,带来高性能,但也带来了同一消息流走了不同路径的可能性,如果没有针对性的规划,从架构上就无法保证消息的顺序。如下图所示,对于同一个 topic 的一条消息流,写入不同的 partition,就会产生多条路径。

在这里插入图片描述

为了确保一条消息流的数据能够严格按照时间顺序被消费,则必须遵循一条路径的原则,这样才能实现 FIFO(First In First Out)。

根据 Kafka 的文档描述,把哪条记录发到哪个 partition,是由 producer 负责:

Producers

Producers publish data to the topics of their choice. The producer is responsible for choosing which record to assign to which partition within the topic. This can be done in a round-robin fashion simply to balance load or it can be done according to some semantic partition function (say based on some key in the record). More on the use of partitioning in a second!

可见,Kafka 已考虑到了确保消息顺序的需求,提供了接口来实现根据指定的 key 值发送到同一 partition 的方法。可以看看 Kafka 相关源码:

具体实现方法

具体实现非常简单,在 producer 发送数据时,选择一个 key,通过 KeyedMessage 方法生成消息,然后 send。以 Java 为例,其他语言可以从 Kafka 文档中找到相同功能的接口:

producer.send(new KeyedMessage<String, String>(topic,key,record))

这个接口,可以让使用者非常方便无需增加代码的情况下来实现指定每个消息流绑定一个 partition 的结果。用户也可以通过自己实现一个 partition 的算法,来实现更精准的 partition 分配控制。具体实现可以参考"kafka 指定 partition 生产,消费"

附上kafka生产者Demo,java代码实现

public class KafkaProducerDemo {
    public static void main(String[] args) {
        //1. 创建配置对象 指定Producer的信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "node1:9092,node2:9092,node3:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class); // 对record的key进行序列化
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        //2. 创建Producer对象
        KafkaProducer<Integer, String> producer = new KafkaProducer<Integer, String>(properties);

        //3. 发布消息
        ProducerRecord<Integer, String> record = new ProducerRecord<Integer, String>("t1",1,"Hello World");
        producer.send(record);

        //4. 提交
        producer.flush();
        producer.close();

    }
}

猜你喜欢

转载自blog.csdn.net/qq_44962429/article/details/106053799