最近在项目中使用Flink的dataStream进行开发,使用Kafka作为source,接入数据,对数据进行清洗转换以后,吐到下游的kafka中.

项目主要步骤:

  1. Kafka作为Flink的source 接入问题.
  2. FlinkKafkaProducer 发送不同的信息到不同的topic,并按照一定规则进行路由
  3. FlinkKafkaProducer保证EXACTLY_ONCE使用的配置问题.

以下是主要的代码实现:

  •  首先Kafka作为Flink的source非常的简单:
FlinkKafkaConsumer011<byte[]> consumer011 = new FlinkKafkaConsumer011(config.getSourceKafkaTopic(),
                new ByteArrayDeserializationSchema(),
                properties(config, kafkaConfig));



        Properties properties = new Properties();
        properties.put("bootstrap.servers", bootstrapServers());
        properties.put("group.id", kafkaGroup());
        properties.put("key.deserializer", ByteArraySerializer.class);
        properties.put("value.deserializer", ByteArraySerializer.class);
        // 每次拉取消息的条数
        properties.put("max.poll.records",1000);

  因为Flink中已经集成了Kafka,只用使用FlinkKafkaConsumer011配置好要接受消息的tipic和序列化类型,下面Properties是具体配置信息.

  • 在讲接入的消息,清洗转换以后,就好sink到下游kafka的topic中,主要使用FlinkKafkaProducer

  在这里要想实现按照消息内容发送到不同的topic,需要重新实现一下 KeyedSerializationSchema接口.在此接口中有三个方法.还可以按照给定的消息key进行信息的路由.在接口的第三个方法 getTargetTopic,就是可以实现按照消息来发送相应的topic.备注:这个接口Deprecated了,但是还是可以使用的.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.flink.streaming.util.serialization;

import java.io.Serializable;
import org.apache.flink.annotation.PublicEvolving;

/** @deprecated */
@Deprecated
@PublicEvolving
public interface KeyedSerializationSchema<T> extends Serializable {
    byte[] serializeKey(T var1);

    byte[] serializeValue(T var1);

    String getTargetTopic(T var1);
}

以下是具体的代码实现:

package com.my.flink.schema;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.flink.api.common.serialization.SerializationSchema;
import org.apache.flink.streaming.connectors.kafka.internals.KafkaSerializationSchemaWrapper;
import org.apache.flink.streaming.connectors.kafka.partitioner.FlinkKafkaPartitioner;
import org.apache.flink.streaming.util.serialization.KeyedSerializationSchema;
import org.apache.kafka.clients.producer.ProducerRecord;

import javax.annotation.Nullable;
import java.nio.charset.Charset;

/**
 *
 * @author nick
 * @date 2021-02-21 11:02:13
 */
@Slf4j
public class MySerializationSchema implements KeyedSerializationSchema<String> {


    @Override
    public byte[] serializeKey(String s) {
        Result ret =   JSON.toObject(s,Result.class);

        String key = ret.getKey();
        if(StringUtils.isNotEmpty(key)){
            return key.getBytes(Charset.defaultCharset());
        }
        //默认发送
        return new byte[0];
    }

    @Override
    public byte[] serializeValue(String s) {
        Result ret =  JSON.toObject(s,Result.class);
        return ret.getMsg().getBytes(Charset.defaultCharset());
    }

    @Override
    public String getTargetTopic(String s) {
        Result ret =  JSON.toObject(s,Result.class);
        log.info(" kafka send message topic : {},sendMsg :{}",ret.getTopic(),ret.getMsg());
        return ret.getTopic();
    }
}



package com.my.flink.dto;

import lombok.Data;

/**
 *
 * @author nick
 * @date 2021-02-21 11:02:13
 */
@Data
public class Result {

    private String msg;

    private String topic;

    private String key;
}

  其中Result中要包含消息要发送的信息和要发送的topic信息,定义如上.

  • Kafka的EXACTLY_ONCE模式配置问题

在Flink中支持Kafka的EXACTLY_ONCE,但是有几个配置点要注意一下:如果要支持此模式,kafka的ACKS_CONFIG应该是ALL模式,还要配置重试次数大于1,还用将kafka中的事务超时时间加长,因为flink中的事务超时时间比kafka中要长会报错(kafka的15分钟,flink中的是1小时)

//不写topic,并设置EXACTLY_ONCE模式
 FlinkKafkaProducer011<String> producer011 = new FlinkKafkaProducer011<String>(
                "",
                new MySerializationSchema(),
                producerProperties(kafkaConfig),
                FlinkKafkaProducer011.Semantic.EXACTLY_ONCE
        );




 private static Properties producerProperties(KafkaConfig kafkaConfig) {
       Properties props = new Properties();

        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServers());
        //设置重试次数
        props.put(ProducerConfig.RETRIES_CONFIG, kafkaConfig.getRetries());
        //达到batchSize大小的时候会发送消息
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, kafkaConfig.getBatchSize());
        //延时时间,延时时间到达之后计算批量发送的大小没达到也发送消息
        props.put(ProducerConfig.LINGER_MS_CONFIG, kafkaConfig.getLinger());
        //缓冲区的值
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, kafkaConfig.getBufferMemory());

        //producer端的消息确认机制,-1和all都表示消息不仅要写入本地的leader中还要写入对应的副本中
        props.put(ProducerConfig.ACKS_CONFIG, "all");
   
        //设置broker响应时间,如果broker在60秒之内还是没有返回给producer确认消息,则认为发送失败
        props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 60000);
        //事务超时时间
        props.put(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG, 900000);
        //每条消息都要确认
        props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, "1");
        //开启幂等
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");


        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);

        return props;

    }

 

 

问题待补充....