1 kafka中的消息写入与自定义分区器

  • 在我们将消息写入kafka的topic时,我们可以通过FlinkkafkaPartitioner指定写入topic的哪个分区。在不指定的情况下,默认的分区器会将每个数据任务映射到一个单独的kafka分区中,即单个任务的所有记录都会发往同一分区。
  • 如果任务数多余分区数,则每个分区可能会包含多个任务发来的记录。
  • 而如果任务数小于分区数,则默认配置会导致有些分区收不到数据。
  • 若此时恰好有使用事件时间的Flink应用消费了该Topic,那么可能会导致问题;
    (理由:在上面我们讲到分配器会在每个分区上定义水位线,然后再对各个分区之间的水位线进行合并)

2 Flink的分区数定义多少个合适?

  • Flink kafka 连接器会以并行的方式获取事件流。每个并行的数据源任务都可以从一个或多个分区中读取数据
    因此当我的程序并发数比较多的情况下,kafka分区数我也设置多点,是不是就意味着我程序处理kafka中topic的数据就会比较高效?

    分区设置的多点好还是不好?
    分区设置的多点既有好处也有坏处,虽然我的分区设置多点,集群的吞吐量会变大,但每个分区也有自己的开销;

    … …

3 FlinkkafkaProducer下的消费保障

注意:
  这里是通过FlinkkafkaProducer将数据发送到kafka;跟下面的检查点是不一样的
FlinkkafkaProducer下的消费保障总共分为3级别
val kafkaSink = new FlinkKafkaProducer[ResultDt]("topicName", kafkaPro, FlinkKafkaProducer.Semantic.EXACTLY_ONCE)

  1. at most once:对于一条message,kafka的receiver最多收到一次(0次或1次)
    (sender把message发送给receiver后,无论receiver是否收到message,sender都不再重新发送message)
  2. at least once(这是默认选项):对于一条message,Kafka的Receiver最少收到一次(1次及以上)
    (sender把message发送给receiver后,当receiver在规定的时间内没有恢复或恢复了error信息,那么sender就会重发,知道sender收到receiver的成功信息)
  3. exactly once:对于一条message,receiver确保只收到一次

4 代码

以下样例代码采用的是flink1.7版本,并依托于数据中的key做分区。

class MySchema extends KeyedSerializationSchema[(String, Int)] {
  override def serializeKey(element: (String, Int)): Array[Byte] = {
    element._2.toString.getBytes() //序列胡key
  }

  override def serializeValue(element: (String, Int)): Array[Byte] = {
    element._1.getBytes() //序列化value
  }

  override def getTargetTopic(element: (String, Int)): String = {
    null //这里返回要发送的topic名字,没什么用,可以不做处理
  }
}

object addSink_kafka_并自定义序列化和分区 extends App {

  import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment

  val streamLocal = StreamExecutionEnvironment.createLocalEnvironment(3)

  import org.apache.flink.api.scala._ //如果数据是有限的(静态数据集)可以引入这个包
  val dataStream: DataStream[(String, Int)] = streamLocal.fromElements(("flink_er", 3), ("f", 1), ("c", 2), ("c", 1), ("d", 5))

  val properties = new Properties()
  properties.setProperty("bootstrap.servers", "master:9092")
  val flinkKafkaProducer = new FlinkKafkaProducer(
    "ceshi01",
    new MySchema(),
    properties,
    Optional.of(new FlinkKafkaPartitioner[(String, Int)] {
      /**
        * @param record      正常的记录
        * @param key         KeyedSerializationSchema中配置的key
        * @param value       KeyedSerializationSchema中配置的value
        * @param targetTopic targetTopic
        * @param partitions  partition列表[0, 1, 2, 3, 4]
        * @return partition
        */
      override def partition(record: (String, Int), key: Array[Byte], value: Array[Byte], targetTopic: String, partitions: Array[Int]): Int = {
        Math.abs(new String(key).hashCode() % partitions.length)
      }
    })
  )
  dataStream.addSink(flinkKafkaProducer)

  streamLocal.execute()
}