• 重点issue flink+kafka乱序问题


    一、问题表象

    由于kafka乱序,导致bridge也乱序,导致绿色正确的数字提前pub,然后被错误的黄色数字覆盖。

    二、问题真正原因

    上图1黄1绿的数据,被pub到partition 0

    上图另1黄的数据,被pub到partition 1

    尽管这三个数据的uk一致,但是被错误的pub到不一致的分区。。。而不同的分区之间是不保证顺序性的,是各自的线程在消费,因为出现了乱序的问题。如果要解决,就必须保证同一个uk被分到同一个partition...那就需要修改kafka的分区策略。

    三、kafka 默认的分区策略

    参考:https://blog.csdn.net/qq_38262266/article/details/107356824 

    • 指明 partition 的情况下,直接将数据放在对应的 partiton ;
    • 没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;
    • 既没有 partition 值又没有 key 值的情况下,第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 round-robin 算法。

    四、通过自定义序列化类,给kafka消息定义key值

    通常我们都是用默认的序列化类(例如SimpleStringSchema)来发送一条消息,并没有指定key值。

    xxxString.addSink(new FlinkKafkaProducer<>("XXX-XXX-TOPIC-1", new SimpleStringSchema(), properties)).name("flink-connectors-kafka");

    有时候我们需要执行发送消息的key,value值,就需要自定义序列化类。由于需要key值,那需要实现KeyedSerializationSchema接口,其有个简单的实现类KeyedSerializationSchemaWrapper,我们只需extends KeyedSerializationSchemaWrapper即可。

    package com.huatai.quant.service.flink.source;
    
    import com.huatai.quant.utils.PartitionUtil;
    import org.apache.flink.api.common.serialization.SerializationSchema;
    import org.apache.flink.streaming.util.serialization.KeyedSerializationSchemaWrapper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    
    public class KafkaCustomSerializationSchema extends KeyedSerializationSchemaWrapper<String> {
        private static Logger logger = LoggerFactory.getLogger(KafkaCustomSerializationSchema.class);
    
        public KafkaCustomSerializationSchema(SerializationSchema serializationSchema) {
            super(serializationSchema);
        }
    
        @Override
        public byte[] serializeKey(String element){
            /**
             *  step 1: convert element to hashmap
             */
            Map<String, String> hashMap = PartitionUtil.convertKafkaJsonToMap(element);
    
            /**
             *  step 2: use hashmap to build uniqueKey
             *  eg: RiskSummaryBO uk --> ATBOOKTRADE12-prop-20220323-FI-FUTURES-2-3
             *   RiskSummaryBO uk partten is : bookName- bookProp- asOfDate- displayType- displaySubType- calcDataSource- summaryType
             */
            List<String> ukAttribute = Arrays.asList("bookName","bookProp","asOfDate","displayType","displaySubType","calcDataSource","summaryType");
            StringBuilder finalKey = new StringBuilder();
            for(int i = 0; i < ukAttribute.size(); i++){
                if(i == ukAttribute.size() -1){
                    finalKey.append(hashMap.get(ukAttribute.get(i)));
                }else {
                    finalKey.append(hashMap.get(ukAttribute.get(i))).append("-");
                }
            }
    
            logger.info("The KEY for kafka message is = " + finalKey.toString());
            return finalKey.toString().getBytes(StandardCharsets.UTF_8);
        }
    }

    注意:

    1.其构造参数必须传入一个SerializationSchema,就可以传入之前提到的普通序列化类SimpleStringSchema实例即可。

    2.KeyedSerializationSchema接口其实也可以serializeValue (猜测:可以对原始消息做修改再pub),再以key, value pub出去。如需要,显示override即可。

    五、如何自定义分区策略

    参数含义(按顺序):

    • 原始消息
    • 提取出来key
    • 提取出来value(猜测:可以对原始消息做一次修改再pub)
    • topic
    • 分区的数组
    package com.huatai.quant.service.flink.source;
    
    import org.apache.flink.streaming.connectors.kafka.partitioner.FlinkKafkaPartitioner;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.Arrays;
    
    public class KafkaCustomPartitioner  extends FlinkKafkaPartitioner <String> {
        private static Logger logger = LoggerFactory.getLogger(KafkaCustomPartitioner.class);
    
        @Override
        public int partition(String s, byte[] bytes, byte[] bytes1, String s2, int[] ints) {
            if(ints != null && ints.length > 0){
                // 按key分配分区
                int partition = Math.abs(Arrays.toString(bytes).hashCode()) % ints.length;
                logger.info("Current element will send to partition = " + partition + " , and totally have " + ints.length + " partitions");
                return partition;
            }
            return 0;
        }
    }

    六、应用新的序列化策略+分区策略

    //old version        
    //riskSummaryString.addSink(new FlinkKafkaProducer<>("FICC-NATS-RISKSUMMARY-1", new SimpleStringSchema(), properties)).name("flink-connectors-kafka");
    
    //new version       
    riskSummaryString.addSink(new FlinkKafkaProducer<>("FICC-NATS-RISKSUMMARY-1", new KafkaCustomSerializationSchema(new SimpleStringSchema()), properties, Optional.of(new KafkaCustomPartitioner()))).name("flink-connectors-kafka");
        

    参考文献

    Flink实战:写入Kafka自定义序列化类和自定义分区器 http://cache.baiducontent.com/c?m=p6eKCqLTI3i2O7VuI0SVEqWZj0N37MUI6zeq7Qnltp8YPeG7yfsRAYGX0ngSSI3ZAD8fUVBaFQY4s-8WmnQkPtpm2S79tmaeUQuzri1HcSjRCE5RifvCnCRWoDM2hgRGce7_FqedQnXJWgTXVEpk3517Iq4waEBZqR0xdHXHP_VMYLcSFBLwxXbllc6oHK9J&p=98769a4799b11cff57eb92204d08&newp=8565841f86cc47a901fcc7710f4492695803ed6339d3d301298ffe0cc4241a1a1a3aecbe25271604d6c37a6002a54a56eafa3770350834f1f689df08d2ecce7e7699&s=fc490ca45c00b124&user=baidu&fm=sc&query=FlinkKafkaProducer+%D7%D4%B6%A8%D2%E5+partitioner&qid=f76a5e9f000004d2&p1=3

    kafka分区Partitioner使用 https://blog.csdn.net/u012129558/article/details/80075597

    Kafka生产者分区partition策略 https://blog.csdn.net/qq_38262266/article/details/107356824

    kafka分区策略 https://www.cnblogs.com/lincf/p/11985026.html

  • 相关阅读:
    软件测试第三次作业2
    软件测试第三次作业1
    软件测试第二次作业2
    软件测试第二次作业1
    只要你足够努力,好运就会降临。
    实验六
    实验二
    个人简介
    第六次作业
    第五次博客作业
  • 原文地址:https://www.cnblogs.com/frankcui/p/16052249.html
Copyright © 2020-2023  润新知