You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by pn...@apache.org on 2020/05/18 12:58:43 UTC
[flink] 02/05: [FLINK-15670][connector] Adds the producer for
KafkaShuffle.
This is an automated email from the ASF dual-hosted git repository.
pnowojski pushed a commit to branch release-1.11
in repository https://gitbox.apache.org/repos/asf/flink.git
commit 2dca425e7fbcb4ed8f28f226e37a49cc58ea7035
Author: Yuan Mei <yu...@gmail.com>
AuthorDate: Mon May 18 16:54:26 2020 +0800
[FLINK-15670][connector] Adds the producer for KafkaShuffle.
KafkaShuffle provides a transparent Kafka source and sink pair, through which the network traffic of a shuffle step is persisted and redirected.
---
.../connectors/kafka/FlinkKafkaProducer.java | 18 +-
.../kafka/shuffle/FlinkKafkaShuffleProducer.java | 213 +++++++++++++++++++++
2 files changed, 224 insertions(+), 7 deletions(-)
diff --git a/flink-connectors/flink-connector-kafka/src/main/java/org/apache/flink/streaming/connectors/kafka/FlinkKafkaProducer.java b/flink-connectors/flink-connector-kafka/src/main/java/org/apache/flink/streaming/connectors/kafka/FlinkKafkaProducer.java
index d4b1b4f..9d033b7 100644
--- a/flink-connectors/flink-connector-kafka/src/main/java/org/apache/flink/streaming/connectors/kafka/FlinkKafkaProducer.java
+++ b/flink-connectors/flink-connector-kafka/src/main/java/org/apache/flink/streaming/connectors/kafka/FlinkKafkaProducer.java
@@ -210,7 +210,7 @@ public class FlinkKafkaProducer<IN>
/**
* The name of the default topic this producer is writing data to.
*/
- private final String defaultTopicId;
+ protected final String defaultTopicId;
/**
* (Serializable) SerializationSchema for turning objects used with Flink into.
@@ -235,7 +235,7 @@ public class FlinkKafkaProducer<IN>
/**
* Partitions of each topic.
*/
- private final Map<String, int[]> topicPartitionsMap;
+ protected final Map<String, int[]> topicPartitionsMap;
/**
* Max number of producers in the pool. If all producers are in use, snapshoting state will throw an exception.
@@ -250,7 +250,7 @@ public class FlinkKafkaProducer<IN>
/**
* Flag controlling whether we are writing the Flink record's timestamp into Kafka.
*/
- private boolean writeTimestampToKafka = false;
+ protected boolean writeTimestampToKafka = false;
/**
* Flag indicating whether to accept failures (and log them), or to fail on failures.
@@ -273,7 +273,7 @@ public class FlinkKafkaProducer<IN>
protected transient volatile Exception asyncException;
/** Number of unacknowledged records. */
- private final AtomicLong pendingRecords = new AtomicLong();
+ protected final AtomicLong pendingRecords = new AtomicLong();
/** Cache of metrics to replace already registered metrics instead of overwriting existing ones. */
private final Map<String, KafkaMetricMutableWrapper> previouslyCreatedMetrics = new HashMap<>();
@@ -1214,7 +1214,7 @@ public class FlinkKafkaProducer<IN>
return producer;
}
- private void checkErroneous() throws FlinkKafkaException {
+ protected void checkErroneous() throws FlinkKafkaException {
Exception e = asyncException;
if (e != null) {
// prevent double throwing
@@ -1256,7 +1256,7 @@ public class FlinkKafkaProducer<IN>
return props;
}
- private static int[] getPartitionsByTopic(String topic, Producer<byte[], byte[]> producer) {
+ protected static int[] getPartitionsByTopic(String topic, Producer<byte[], byte[]> producer) {
// the fetched list is immutable, so we're creating a mutable copy in order to sort it
List<PartitionInfo> partitionsList = new ArrayList<>(producer.partitionsFor(topic));
@@ -1281,7 +1281,7 @@ public class FlinkKafkaProducer<IN>
*/
@VisibleForTesting
@Internal
- static class KafkaTransactionState {
+ protected static class KafkaTransactionState {
private final transient FlinkKafkaInternalProducer<byte[], byte[]> producer;
@@ -1315,6 +1315,10 @@ public class FlinkKafkaProducer<IN>
return transactionalId != null;
}
+ public FlinkKafkaInternalProducer<byte[], byte[]> getProducer() {
+ return producer;
+ }
+
@Override
public String toString() {
return String.format(
diff --git a/flink-connectors/flink-connector-kafka/src/main/java/org/apache/flink/streaming/connectors/kafka/shuffle/FlinkKafkaShuffleProducer.java b/flink-connectors/flink-connector-kafka/src/main/java/org/apache/flink/streaming/connectors/kafka/shuffle/FlinkKafkaShuffleProducer.java
new file mode 100644
index 0000000..6be2bba
--- /dev/null
+++ b/flink-connectors/flink-connector-kafka/src/main/java/org/apache/flink/streaming/connectors/kafka/shuffle/FlinkKafkaShuffleProducer.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.streaming.connectors.kafka.shuffle;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.api.common.typeutils.TypeSerializer;
+import org.apache.flink.api.java.functions.KeySelector;
+import org.apache.flink.core.memory.DataOutputSerializer;
+import org.apache.flink.runtime.state.KeyGroupRangeAssignment;
+import org.apache.flink.streaming.api.watermark.Watermark;
+import org.apache.flink.streaming.connectors.kafka.FlinkKafkaException;
+import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer;
+import org.apache.flink.util.Preconditions;
+import org.apache.flink.util.PropertiesUtil;
+
+import org.apache.kafka.clients.producer.ProducerRecord;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Properties;
+
+import static org.apache.flink.streaming.connectors.kafka.shuffle.FlinkKafkaShuffle.PARTITION_NUMBER;
+
+/**
+ * Flink Kafka Shuffle Producer Function.
+ * It is different from {@link FlinkKafkaProducer} in the way handling elements and watermarks
+ */
+@Internal
+public class FlinkKafkaShuffleProducer<IN, KEY> extends FlinkKafkaProducer<IN> {
+ private final KafkaSerializer<IN> kafkaSerializer;
+ private final KeySelector<IN, KEY> keySelector;
+ private final int numberOfPartitions;
+
+ FlinkKafkaShuffleProducer(
+ String defaultTopicId,
+ TypeSerializer<IN> typeSerializer,
+ Properties props,
+ KeySelector<IN, KEY> keySelector,
+ Semantic semantic,
+ int kafkaProducersPoolSize) {
+ super(defaultTopicId, (element, timestamp) -> null, props, semantic, kafkaProducersPoolSize);
+
+ this.kafkaSerializer = new KafkaSerializer<>(typeSerializer);
+ this.keySelector = keySelector;
+
+ Preconditions.checkArgument(
+ props.getProperty(PARTITION_NUMBER) != null,
+ "Missing partition number for Kafka Shuffle");
+ numberOfPartitions = PropertiesUtil.getInt(props, PARTITION_NUMBER, Integer.MIN_VALUE);
+ }
+
+ /**
+ * This is the function invoked to handle each element.
+ *
+ * @param transaction Transaction state;
+ * elements are written to Kafka in transactions to guarantee different level of data consistency
+ * @param next Element to handle
+ * @param context Context needed to handle the element
+ * @throws FlinkKafkaException for kafka error
+ */
+ @Override
+ public void invoke(KafkaTransactionState transaction, IN next, Context context) throws FlinkKafkaException {
+ checkErroneous();
+
+ // write timestamp to Kafka if timestamp is available
+ Long timestamp = context.timestamp();
+
+ int[] partitions = getPartitions(transaction);
+ int partitionIndex;
+ try {
+ partitionIndex = KeyGroupRangeAssignment
+ .assignKeyToParallelOperator(keySelector.getKey(next), partitions.length, partitions.length);
+ } catch (Exception e) {
+ throw new RuntimeException("Fail to assign a partition number to record", e);
+ }
+
+ ProducerRecord<byte[], byte[]> record = new ProducerRecord<>(
+ defaultTopicId,
+ partitionIndex,
+ timestamp,
+ null,
+ kafkaSerializer.serializeRecord(next, timestamp));
+
+ pendingRecords.incrementAndGet();
+ transaction.getProducer().send(record, callback);
+ }
+
+ /**
+ * This is the function invoked to handle each watermark.
+ *
+ * @param watermark Watermark to handle
+ * @throws FlinkKafkaException For kafka error
+ */
+ public void invoke(Watermark watermark) throws FlinkKafkaException {
+ checkErroneous();
+ KafkaTransactionState transaction = currentTransaction();
+
+ int[] partitions = getPartitions(transaction);
+ int subtask = getRuntimeContext().getIndexOfThisSubtask();
+
+ // broadcast watermark
+ long timestamp = watermark.getTimestamp();
+ for (int partition : partitions) {
+ ProducerRecord<byte[], byte[]> record = new ProducerRecord<>(
+ defaultTopicId,
+ partition,
+ timestamp,
+ null,
+ kafkaSerializer.serializeWatermark(watermark, subtask));
+
+ pendingRecords.incrementAndGet();
+ transaction.getProducer().send(record, callback);
+ }
+ }
+
+ private int[] getPartitions(KafkaTransactionState transaction) {
+ int[] partitions = topicPartitionsMap.get(defaultTopicId);
+ if (partitions == null) {
+ partitions = getPartitionsByTopic(defaultTopicId, transaction.getProducer());
+ topicPartitionsMap.put(defaultTopicId, partitions);
+ }
+
+ Preconditions.checkArgument(partitions.length == numberOfPartitions);
+
+ return partitions;
+ }
+
+ /**
+ * Flink Kafka Shuffle Serializer.
+ */
+ public static final class KafkaSerializer<IN> implements Serializable {
+ public static final int TAG_REC_WITH_TIMESTAMP = 0;
+ public static final int TAG_REC_WITHOUT_TIMESTAMP = 1;
+ public static final int TAG_WATERMARK = 2;
+
+ private static final long serialVersionUID = 2000002L;
+ // easy for updating SerDe format later
+ private static final int KAFKA_SHUFFLE_VERSION = 0;
+
+ private final TypeSerializer<IN> serializer;
+
+ private transient DataOutputSerializer dos;
+
+ KafkaSerializer(TypeSerializer<IN> serializer) {
+ this.serializer = serializer;
+ }
+
+ /**
+ * Format: Version(byte), TAG(byte), [timestamp(long)], record.
+ */
+ byte[] serializeRecord(IN record, Long timestamp) {
+ if (dos == null) {
+ dos = new DataOutputSerializer(16);
+ }
+
+ try {
+ dos.write(KAFKA_SHUFFLE_VERSION);
+
+ if (timestamp == null) {
+ dos.write(TAG_REC_WITHOUT_TIMESTAMP);
+ } else {
+ dos.write(TAG_REC_WITH_TIMESTAMP);
+ dos.writeLong(timestamp);
+ }
+ serializer.serialize(record, dos);
+
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to serialize record", e);
+ }
+
+ byte[] ret = dos.getCopyOfBuffer();
+ dos.clear();
+ return ret;
+ }
+
+ /**
+ * Format: Version(byte), TAG(byte), subtask(int), timestamp(long).
+ */
+ byte[] serializeWatermark(Watermark watermark, int subtask) {
+ if (dos == null) {
+ dos = new DataOutputSerializer(16);
+ }
+
+ try {
+ dos.write(KAFKA_SHUFFLE_VERSION);
+ dos.write(TAG_WATERMARK);
+ dos.writeInt(subtask);
+ dos.writeLong(watermark.getTimestamp());
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to serialize watermark", e);
+ }
+
+ byte[] ret = dos.getCopyOfBuffer();
+ dos.clear();
+ return ret;
+ }
+ }
+}