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