重点issue - kafka分区技术选型迭代+kafka乱序问题

回到顶部(go to top)

选型一:为了保证消息有序,只有一个分区partition

优点:

(前提:生产者发送消息的顺序,和消息到达kafka的顺序一致)...

为了避免多partition,导致消费者消费某些顺序敏感的数据,会出现乱序的现象。

 

缺点:

当数据量巨大的时候,所有该topic的数据都挤在同一个partition。这样子就无法发挥kafka的并发度。

如果是这种情况,同一个消费group里,哪怕有多个消费者,但每个消费者只能选择某个topic的一个partition消费。该哪怕消费者再多,也无法加快消费速度。

 

 

回到顶部(go to top)

选型二:为提升并发度,增加partition

选型二下方描述的问题,是发生在结构图的下半部分。

RiskSummary服务充当发布者,计划把结果发布到kafka时, 原本没有“优化点:根据UK分区”的时候,会将同一个UK的数据,随机放在partition 0/1, 继而有可能导致最终结果出现了乱序。

(前提依旧成立:生产者发送消息的顺序,和消息到达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

 

回到顶部(go to top)

选型三:继续优化 -- 指定分区消费;避免rebalance; 保证消息顺序到达

consumer如何指定消费的partition

看下方结构图的上半部分:

当生产者的数据根据品种进行了分区(债券,非债券),消费者此时也是拆成了多个实例(债券,非债券)。

为了让“债券”消费者,指定消费“债券”partition0,需要使用这个方法:consumer.assign()直接消息指定分区

 

如何避免消费者rebalance,导致消费者消费的分区发生变化

设置consumer端参数partition.assignment.strategy=Sticky (再平衡时需要放弃原有分区) / CooperativeSticky (再平衡时不必放弃原有分区),这是因为Sticky算法会最大化保证消费分区方案的不变更。假设你的因果消息都有相同的key,那么结合Sticky算法有可能保证即使出现rebalance,要消费的分区依然有原来的consumer负责。

 

如何保证消费到达kafka顺序,消息发送顺序一致

一个生产者,发两次消息,但是网络原因,消息到达的顺序和消息发送的顺序不一致.
防止乱序可以通过设置max.in.flight.requests.per.connection=1来保证

 

 

 

回到顶部(go to top)

选型四:继续优化 -- 不同品种的消息,生产速录不一致怎么办

假如我现在的业务数据定义了三个key,但是这三个key对应的消息生产速率不一致,按照老师上面的示意图展示的是,特定的key只会存储在特定的一个分区中,那岂不是牺牲了拓展性么,如果其中一个key的生产速率非常大,而另外2个key没那么大,却会一直占用分区,不会造成分区的空间浪费吗?

 

其实在生产环境中用key做逻辑区分并不太常见。如果不同key速率相差很大,可以考虑使用不同的topic

 

posted on   frank_cui  阅读(953)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2019-03-24 Leetcode - 238. Product of Array Except Self
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

levels of contents
点击右上角即可分享
微信分享提示