org.apache.flink.runtime.query.netty.message.KvStateRequestSerializer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flink.runtime.query.netty.message.KvStateRequestSerializer.java

Source

/*
 * 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.runtime.query.netty.message;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import org.apache.flink.api.common.typeutils.TypeSerializer;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.runtime.query.KvStateID;
import org.apache.flink.runtime.query.netty.KvStateClient;
import org.apache.flink.runtime.query.netty.KvStateServer;
import org.apache.flink.runtime.util.DataInputDeserializer;
import org.apache.flink.runtime.util.DataOutputSerializer;
import org.apache.flink.util.Preconditions;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Serialization and deserialization of messages exchanged between
 * {@link KvStateClient} and {@link KvStateServer}.
 *
 * <p>The binary messages have the following format:
 *
 * <pre>
 *                     <------ Frame ------------------------->
 *                    +----------------------------------------+
 *                    |        HEADER (8)      | PAYLOAD (VAR) |
 * +------------------+----------------------------------------+
 * | FRAME LENGTH (4) | VERSION (4) | TYPE (4) | CONTENT (VAR) |
 * +------------------+----------------------------------------+
 * </pre>
 *
 * <p>The concrete content of a message depends on the {@link KvStateRequestType}.
 */
public final class KvStateRequestSerializer {

    /** The serialization version ID. */
    private static final int VERSION = 0x79a1b710;

    /** Byte length of the header. */
    private static final int HEADER_LENGTH = 8;

    // ------------------------------------------------------------------------
    // Serialization
    // ------------------------------------------------------------------------

    /**
     * Allocates a buffer and serializes the KvState request into it.
     *
     * @param alloc                     ByteBuf allocator for the buffer to
     *                                  serialize message into
     * @param requestId                 ID for this request
     * @param kvStateId                 ID of the requested KvState instance
     * @param serializedKeyAndNamespace Serialized key and namespace to request
     *                                  from the KvState instance.
     * @return Serialized KvState request message
     */
    public static ByteBuf serializeKvStateRequest(ByteBufAllocator alloc, long requestId, KvStateID kvStateId,
            byte[] serializedKeyAndNamespace) {

        // Header + request ID + KvState ID + Serialized namespace
        int frameLength = HEADER_LENGTH + 8 + (8 + 8) + (4 + serializedKeyAndNamespace.length);
        ByteBuf buf = alloc.ioBuffer(frameLength + 4); // +4 for frame length

        buf.writeInt(frameLength);

        writeHeader(buf, KvStateRequestType.REQUEST);

        buf.writeLong(requestId);
        buf.writeLong(kvStateId.getLowerPart());
        buf.writeLong(kvStateId.getUpperPart());
        buf.writeInt(serializedKeyAndNamespace.length);
        buf.writeBytes(serializedKeyAndNamespace);

        return buf;
    }

    /**
     * Allocates a buffer and serializes the KvState request result into it.
     *
     * @param alloc             ByteBuf allocator for the buffer to serialize message into
     * @param requestId         ID for this request
     * @param serializedResult  Serialized Result
     * @return Serialized KvState request result message
     */
    public static ByteBuf serializeKvStateRequestResult(ByteBufAllocator alloc, long requestId,
            byte[] serializedResult) {

        Preconditions.checkNotNull(serializedResult, "Serialized result");

        // Header + request ID + serialized result
        int frameLength = HEADER_LENGTH + 8 + 4 + serializedResult.length;

        ByteBuf buf = alloc.ioBuffer(frameLength);

        buf.writeInt(frameLength);
        writeHeader(buf, KvStateRequestType.REQUEST_RESULT);
        buf.writeLong(requestId);

        buf.writeInt(serializedResult.length);
        buf.writeBytes(serializedResult);

        return buf;
    }

    /**
     * Allocates a buffer and serializes the KvState request failure into it.
     *
     * @param alloc ByteBuf allocator for the buffer to serialize message into
     * @param requestId ID of the request responding to
     * @param cause Failure cause
     * @return Serialized KvState request failure message
     * @throws IOException Serialization failures are forwarded
     */
    public static ByteBuf serializeKvStateRequestFailure(ByteBufAllocator alloc, long requestId, Throwable cause)
            throws IOException {

        ByteBuf buf = alloc.ioBuffer();

        // Frame length is set at the end
        buf.writeInt(0);

        writeHeader(buf, KvStateRequestType.REQUEST_FAILURE);

        // Message
        buf.writeLong(requestId);

        try (ByteBufOutputStream bbos = new ByteBufOutputStream(buf);
                ObjectOutputStream out = new ObjectOutputStream(bbos)) {

            out.writeObject(cause);
        }

        // Set frame length
        int frameLength = buf.readableBytes() - 4;
        buf.setInt(0, frameLength);

        return buf;
    }

    /**
     * Allocates a buffer and serializes the server failure into it.
     *
     * <p>The cause must not be or contain any user types as causes.
     *
     * @param alloc ByteBuf allocator for the buffer to serialize message into
     * @param cause Failure cause
     * @return Serialized server failure message
     * @throws IOException Serialization failures are forwarded
     */
    public static ByteBuf serializeServerFailure(ByteBufAllocator alloc, Throwable cause) throws IOException {
        ByteBuf buf = alloc.ioBuffer();

        // Frame length is set at end
        buf.writeInt(0);

        writeHeader(buf, KvStateRequestType.SERVER_FAILURE);

        try (ByteBufOutputStream bbos = new ByteBufOutputStream(buf);
                ObjectOutputStream out = new ObjectOutputStream(bbos)) {

            out.writeObject(cause);
        }

        // Set frame length
        int frameLength = buf.readableBytes() - 4;
        buf.setInt(0, frameLength);

        return buf;
    }

    // ------------------------------------------------------------------------
    // Deserialization
    // ------------------------------------------------------------------------

    /**
     * Deserializes the header and returns the request type.
     *
     * @param buf Buffer to deserialize (expected to be at header position)
     * @return Deserialzied request type
     * @throws IllegalArgumentException If unexpected message version or message type
     */
    public static KvStateRequestType deserializeHeader(ByteBuf buf) {
        // Check the version
        int version = buf.readInt();
        if (version != VERSION) {
            throw new IllegalArgumentException(
                    "Illegal message version " + version + ". Expected: " + VERSION + ".");
        }

        // Get the message type
        int msgType = buf.readInt();
        KvStateRequestType[] values = KvStateRequestType.values();
        if (msgType >= 0 && msgType <= values.length) {
            return values[msgType];
        } else {
            throw new IllegalArgumentException("Illegal message type with index " + msgType);
        }
    }

    /**
     * Deserializes the KvState request message.
     *
     * <p><strong>Important</strong>: the returned buffer is sliced from the
     * incoming ByteBuf stream and retained. Therefore, it needs to be recycled
     * by the consumer.
     *
     * @param buf Buffer to deserialize (expected to be positioned after header)
     * @return Deserialized KvStateRequest
     */
    public static KvStateRequest deserializeKvStateRequest(ByteBuf buf) {
        long requestId = buf.readLong();
        KvStateID kvStateId = new KvStateID(buf.readLong(), buf.readLong());

        // Serialized key and namespace
        int length = buf.readInt();

        if (length < 0) {
            throw new IllegalArgumentException(
                    "Negative length for serialized key and namespace. " + "This indicates a serialization error.");
        }

        // Copy the buffer in order to be able to safely recycle the ByteBuf
        byte[] serializedKeyAndNamespace = new byte[length];
        if (length > 0) {
            buf.readBytes(serializedKeyAndNamespace);
        }

        return new KvStateRequest(requestId, kvStateId, serializedKeyAndNamespace);
    }

    /**
     * Deserializes the KvState request result.
     *
     * @param buf Buffer to deserialize (expected to be positioned after header)
     * @return Deserialized KvStateRequestResult
     */
    public static KvStateRequestResult deserializeKvStateRequestResult(ByteBuf buf) {
        long requestId = buf.readLong();

        // Serialized KvState
        int length = buf.readInt();

        if (length < 0) {
            throw new IllegalArgumentException(
                    "Negative length for serialized result. " + "This indicates a serialization error.");
        }

        byte[] serializedValue = new byte[length];

        if (length > 0) {
            buf.readBytes(serializedValue);
        }

        return new KvStateRequestResult(requestId, serializedValue);
    }

    /**
     * Deserializes the KvState request failure.
     *
     * @param buf Buffer to deserialize (expected to be positioned after header)
     * @return Deserialized KvStateRequestFailure
     */
    public static KvStateRequestFailure deserializeKvStateRequestFailure(ByteBuf buf)
            throws IOException, ClassNotFoundException {
        long requestId = buf.readLong();

        Throwable cause;
        try (ByteBufInputStream bbis = new ByteBufInputStream(buf);
                ObjectInputStream in = new ObjectInputStream(bbis)) {

            cause = (Throwable) in.readObject();
        }

        return new KvStateRequestFailure(requestId, cause);
    }

    /**
     * Deserializes the KvState request failure.
     *
     * @param buf Buffer to deserialize (expected to be positioned after header)
     * @return Deserialized KvStateRequestFailure
     * @throws IOException            Serialization failure are forwarded
     * @throws ClassNotFoundException If Exception type can not be loaded
     */
    public static Throwable deserializeServerFailure(ByteBuf buf) throws IOException, ClassNotFoundException {
        try (ByteBufInputStream bbis = new ByteBufInputStream(buf);
                ObjectInputStream in = new ObjectInputStream(bbis)) {

            return (Throwable) in.readObject();
        }
    }

    // ------------------------------------------------------------------------
    // Generic serialization utils
    // ------------------------------------------------------------------------

    /**
     * Serializes the key and namespace into a {@link ByteBuffer}.
     *
     * <p>The serialized format matches the RocksDB state backend key format, i.e.
     * the key and namespace don't have to be deserialized for RocksDB lookups.
     *
     * @param key                 Key to serialize
     * @param keySerializer       Serializer for the key
     * @param namespace           Namespace to serialize
     * @param namespaceSerializer Serializer for the namespace
     * @param <K>                 Key type
     * @param <N>                 Namespace type
     * @return Buffer holding the serialized key and namespace
     * @throws IOException Serialization errors are forwarded
     */
    public static <K, N> byte[] serializeKeyAndNamespace(K key, TypeSerializer<K> keySerializer, N namespace,
            TypeSerializer<N> namespaceSerializer) throws IOException {

        DataOutputSerializer dos = new DataOutputSerializer(32);

        keySerializer.serialize(key, dos);
        dos.writeByte(42);
        namespaceSerializer.serialize(namespace, dos);

        return dos.getCopyOfBuffer();
    }

    /**
     * Deserializes the key and namespace into a {@link Tuple2}.
     *
     * @param serializedKeyAndNamespace Serialized key and namespace
     * @param keySerializer             Serializer for the key
     * @param namespaceSerializer       Serializer for the namespace
     * @param <K>                       Key type
     * @param <N>                       Namespace
     * @return Tuple2 holding deserialized key and namespace
     * @throws IOException              if the deserialization fails for any reason
     */
    public static <K, N> Tuple2<K, N> deserializeKeyAndNamespace(byte[] serializedKeyAndNamespace,
            TypeSerializer<K> keySerializer, TypeSerializer<N> namespaceSerializer) throws IOException {

        DataInputDeserializer dis = new DataInputDeserializer(serializedKeyAndNamespace, 0,
                serializedKeyAndNamespace.length);

        try {
            K key = keySerializer.deserialize(dis);
            byte magicNumber = dis.readByte();
            if (magicNumber != 42) {
                throw new IOException("Unexpected magic number " + magicNumber + ".");
            }
            N namespace = namespaceSerializer.deserialize(dis);

            if (dis.available() > 0) {
                throw new IOException("Unconsumed bytes in the serialized key and namespace.");
            }

            return new Tuple2<>(key, namespace);
        } catch (IOException e) {
            throw new IOException(
                    "Unable to deserialize key " + "and namespace. This indicates a mismatch in the key/namespace "
                            + "serializers used by the KvState instance and this access.",
                    e);
        }
    }

    /**
     * Serializes the value with the given serializer.
     *
     * @param value      Value of type T to serialize
     * @param serializer Serializer for T
     * @param <T>        Type of the value
     * @return Serialized value or <code>null</code> if value <code>null</code>
     * @throws IOException On failure during serialization
     */
    public static <T> byte[] serializeValue(T value, TypeSerializer<T> serializer) throws IOException {
        if (value != null) {
            // Serialize
            DataOutputSerializer dos = new DataOutputSerializer(32);
            serializer.serialize(value, dos);
            return dos.getCopyOfBuffer();
        } else {
            return null;
        }
    }

    /**
     * Deserializes the value with the given serializer.
     *
     * @param serializedValue Serialized value of type T
     * @param serializer      Serializer for T
     * @param <T>             Type of the value
     * @return Deserialized value or <code>null</code> if the serialized value
     * is <code>null</code>
     * @throws IOException On failure during deserialization
     */
    public static <T> T deserializeValue(byte[] serializedValue, TypeSerializer<T> serializer) throws IOException {
        if (serializedValue == null) {
            return null;
        } else {
            final DataInputDeserializer deser = new DataInputDeserializer(serializedValue, 0,
                    serializedValue.length);
            final T value = serializer.deserialize(deser);
            if (deser.available() > 0) {
                throw new IOException("Unconsumed bytes in the deserialized value. "
                        + "This indicates a mismatch in the value serializers "
                        + "used by the KvState instance and this access.");
            }
            return value;
        }
    }

    /**
     * Deserializes all values with the given serializer.
     *
     * @param serializedValue Serialized value of type List<T>
     * @param serializer      Serializer for T
     * @param <T>             Type of the value
     * @return Deserialized list or <code>null</code> if the serialized value
     * is <code>null</code>
     * @throws IOException On failure during deserialization
     */
    public static <T> List<T> deserializeList(byte[] serializedValue, TypeSerializer<T> serializer)
            throws IOException {
        if (serializedValue != null) {
            final DataInputDeserializer in = new DataInputDeserializer(serializedValue, 0, serializedValue.length);

            try {
                final List<T> result = new ArrayList<>();
                while (in.available() > 0) {
                    result.add(serializer.deserialize(in));

                    // The expected binary format has a single byte separator. We
                    // want a consistent binary format in order to not need any
                    // special casing during deserialization. A "cleaner" format
                    // would skip this extra byte, but would require a memory copy
                    // for RocksDB, which stores the data serialized in this way
                    // for lists.
                    if (in.available() > 0) {
                        in.readByte();
                    }
                }

                return result;
            } catch (IOException e) {
                throw new IOException(
                        "Unable to deserialize value. " + "This indicates a mismatch in the value serializers "
                                + "used by the KvState instance and this access.",
                        e);
            }
        } else {
            return null;
        }
    }

    /**
     * Serializes all values of the Iterable with the given serializer.
     *
     * @param entries         Key-value pairs to serialize
     * @param keySerializer   Serializer for UK
     * @param valueSerializer Serializer for UV
     * @param <UK>            Type of the keys
     * @param <UV>            Type of the values
     * @return Serialized values or <code>null</code> if values <code>null</code> or empty
     * @throws IOException On failure during serialization
     */
    public static <UK, UV> byte[] serializeMap(Iterable<Map.Entry<UK, UV>> entries,
            TypeSerializer<UK> keySerializer, TypeSerializer<UV> valueSerializer) throws IOException {
        if (entries != null) {
            // Serialize
            DataOutputSerializer dos = new DataOutputSerializer(32);

            for (Map.Entry<UK, UV> entry : entries) {
                keySerializer.serialize(entry.getKey(), dos);

                if (entry.getValue() == null) {
                    dos.writeBoolean(true);
                } else {
                    dos.writeBoolean(false);
                    valueSerializer.serialize(entry.getValue(), dos);
                }
            }

            return dos.getCopyOfBuffer();
        } else {
            return null;
        }
    }

    /**
     * Deserializes all kv pairs with the given serializer.
     *
     * @param serializedValue Serialized value of type Map<UK, UV>
     * @param keySerializer   Serializer for UK
     * @param valueSerializer Serializer for UV
     * @param <UK>            Type of the key
     * @param <UV>            Type of the value.
     * @return Deserialized map or <code>null</code> if the serialized value
     * is <code>null</code>
     * @throws IOException On failure during deserialization
     */
    public static <UK, UV> Map<UK, UV> deserializeMap(byte[] serializedValue, TypeSerializer<UK> keySerializer,
            TypeSerializer<UV> valueSerializer) throws IOException {
        if (serializedValue != null) {
            DataInputDeserializer in = new DataInputDeserializer(serializedValue, 0, serializedValue.length);

            Map<UK, UV> result = new HashMap<>();
            while (in.available() > 0) {
                UK key = keySerializer.deserialize(in);

                boolean isNull = in.readBoolean();
                UV value = isNull ? null : valueSerializer.deserialize(in);

                result.put(key, value);
            }

            return result;
        } else {
            return null;
        }
    }

    // ------------------------------------------------------------------------

    /**
     * Helper for writing the header.
     *
     * @param buf         Buffer to serialize header into
     * @param requestType Result type to serialize
     */
    private static void writeHeader(ByteBuf buf, KvStateRequestType requestType) {
        buf.writeInt(VERSION);
        buf.writeInt(requestType.ordinal());
    }
}