Flink Sink Kafka 自定义Topic与自定义分区

Flink Sink Kafka 自定义Topic与自定义分区

需求背景:

Flink处理完成数据后,需要将消息传给Kafka,为了避免数据倾斜,根据消息的主键进行Hash取模,自定义输出到对应的Topic,为了提升从Kafka读取消息的速度,将相同主键的消息存放到同一分区。

解决方案:

Flink-Kafka连接器中有一个可以传递序列化类和分区器的构造方法,我们可以重写这两个方法实现自定义Topic和自定义分区,具体方法如下:

@Deprecated
	public FlinkKafkaProducer010(
        String topicId,  //Topic名称
        SerializationSchema<T> serializationSchema,  //序列化类
        Properties producerConfig,    //kafka producer的属性
        KafkaPartitioner<T> customPartitioner   //自定义分区器
    ) {
		this(topicId, new KeyedSerializationSchemaWrapper<>(serializationSchema), producerConfig, customPartitioner);
	}
  • 数据准备

    • 主键在消息字段中的下标
    • 自定义的Topic列表
  • 代码实现

    • 自定义序列化

      • 实现KeyedSerializationSchema接口
      • 构造传参的构造函数
      • 重写serializeKey方法、serializeValue方法和getTargetTopic方法
      public class MySchema implements KeyedSerializationSchema<String> {
          int primaryKeyIndex ; //主键下表
          List<Map> topicList ; //Topic列表
          String topicName;     //取模确定的Topic名称
      
          public MySchema(int primaryKeyIndex,List<Map> topicList){
              this.primaryKeyIndex = primaryKeyIndex;
              this.topicList = topicList;
          }
      
          @Override
          public byte[] serializeKey(String element) {
              //element就是flink流中的真实数据,这里可以根据主键下标得到主键的值key,然后下面分区器就根据这个key去决定发送到kafka哪个分区中
              topicName = element.split(":")[primaryKeyIndex + 2];
              //取出key后要转成字节数组返回
              return topicName.getBytes();
          }
      
          @Override
          public byte[] serializeValue(String element) {
              //要序列化的value,这里一般就原封不动的转成字节数组就行了
              return element.toString().getBytes();
          }
      
          @Override
          public String getTargetTopic(String element) {
              //这里返回要发送的topic名字
              //主键取模
              int hashCode = Math.abs(topicName.hashCode());
              int totalHashIndex = hashCode % (topicList.size() * 10);
              //topic列表的下标
              int topicIndex = totalHashIndex % 10;
              //topic的名字
              String topic= (String) topicList.get(topicIndex).get("TOPIC");
              //打印验证 
       System.out.println("topic:"+topic+"+hashCode:"+hashCode+"+totalHashIndex:"+totalHashIndex+"+topicIndex:"+topicIndex);
              return topic;
          }
      }
      
    • 自定义分区器

      • 继承FlinkKafkaPartitioner类
      • 重写partition方法
      public class MyPartitioner extends FlinkKafkaPartitioner {
          @Override
          public int partition(Object record, byte[] key, byte[] value, String targetTopic, int[] partitions) {
              //这里接收到的key是上面MySchema()中序列化后的key,需要转成string,然后取key的hash值取模kafka分区数量
              int part = Math.abs(new String(key).hashCode() % partitions.length);
              //打印验证 
              System.out.println("part:"+part+"+size:"+partitions.length);
              return part;
          }
      }
      
    • 传入参数

      FlinkKafkaProducer010<String> producer =
      				new FlinkKafkaProducer010<String>("destTopic", new MySchema(primaryKeyIndex,topicList), props,new MyPartitioner());
      

      这里的destTopic可以是任意字符串

  • 方案结果
    这里我定义了两个Topic,分别是event_topic_1和event_topic_2,每个Topic有3个分区在这里插入图片描述
    在这里插入图片描述

注意事项:

在实现KeyedSerializationSchema接口时,泛型一定要使用String。开始时使用Object报错,因为KeyedSerializationSchema实例泛型类型不同,导致不能序列化。

引用:

本文参考了https://blog.csdn.net/qq_24124009/article/details/89505189

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章