web-dev-qa-db-fra.com

Kafka: écriture d'un sérialiseur personnalisé

J'essaie de construire un POC avec Kafka 0.8.1. J'utilise ma propre classe Java comme une Kafka message qui a un tas de types de données String. Je ne peux pas utiliser la classe de sérialiseur par défaut ou la classe de sérialiseur String fournie avec la bibliothèque Kafka. Je suppose que je dois écrire mon propre sérialiseur et le nourrir dans les propriétés du producteur. Si vous savez écrire un exemple de sérialiseur personnalisé en Kafka (en Java), merci de partager. Merci beaucoup. Merci beaucoup.

20
Paaji

Les éléments requis pour écrire un sérialiseur personnalisé sont les suivants:

  1. Implémentez Encoder avec un objet spécifié pour le générique
    • La fourniture d'un constructeur VerifiableProperties est requise
  2. Substitue la méthode toBytes(...) en s'assurant qu'un tableau d'octets est retourné
  3. Injectez la classe sérialiseur dans ProducerConfig

Déclaration d'un sérialiseur personnalisé pour un producteur

Comme vous l'avez noté dans votre question, Kafka fournit un moyen de déclarer un sérialiseur spécifique pour un producteur. La classe de sérialiseur est définie dans une instance ProducerConfig et cette instance est utilisée pour construire la classe Producer souhaitée.

Si vous suivez Exemple de producteur de Kafka vous construirez ProducerConfig via un objet Properties. Lors de la création de votre fichier de propriétés, assurez-vous d'inclure:

props.put("serializer.class", "path.to.your.CustomSerializer");

Avec le chemin d'accès à la classe que vous souhaitez Kafka à utiliser pour sérialiser les messages avant de les ajouter au journal.

Création d'un sérialiseur personnalisé qui Kafka comprend

L'écriture d'un sérialiseur personnalisé que Kafka peut interpréter correctement nécessite la mise en œuvre du Encoder[T] scala classe que Kafka fournit. Implémentation des traits dans Java est bizarre , mais la méthode suivante a fonctionné pour sérialiser JSON dans mon projet:

public class JsonEncoder implements Encoder<Object> {
    private static final Logger logger = Logger.getLogger(JsonEncoder.class);
    // instantiating ObjectMapper is expensive. In real life, prefer injecting the value.
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public JsonEncoder(VerifiableProperties verifiableProperties) {
        /* This constructor must be present for successful compile. */
    }

    @Override
    public byte[] toBytes(Object object) {
        try {
            return objectMapper.writeValueAsString(object).getBytes();
        } catch (JsonProcessingException e) {
            logger.error(String.format("Json processing failed for object: %s", object.getClass().getName()), e);
        }
        return "".getBytes();
    }
}

Votre question donne l'impression que vous utilisez un seul objet (appelons-le CustomMessage) pour tous les messages ajoutés à votre journal. Si tel est le cas, votre sérialiseur pourrait ressembler davantage à ceci:

package com.project.serializer;

public class CustomMessageEncoder implements Encoder<CustomMessage> {
    public CustomMessageEncoder(VerifiableProperties verifiableProperties) {
        /* This constructor must be present for successful compile. */
    }

    @Override
    public byte[] toBytes(CustomMessage customMessage) {
        return customMessage.toBytes();
    }
}

Ce qui laisserait votre configuration de propriété ressembler à ceci:

props.put("serializer.class", "path.to.your.CustomSerializer");
41
Sam Berry

Vous devez implémenter à la fois le codage et le décodeur

public class JsonEncoder implements Encoder<Object> {
        private static final Logger LOGGER = Logger.getLogger(JsonEncoder.class);

        public JsonEncoder(VerifiableProperties verifiableProperties) {
            /* This constructor must be present for successful compile. */
        }

        @Override
        public byte[] toBytes(Object object) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                return objectMapper.writeValueAsString(object).getBytes();
            } catch (JsonProcessingException e) {
                LOGGER.error(String.format("Json processing failed for object: %s", object.getClass().getName()), e);
            }
            return "".getBytes();
        }
    }

Le code du décodeur

public class JsonDecoder  implements Decoder<Object> {
    private static final Logger LOGGER = Logger.getLogger(JsonEncoder.class);
    public JsonDecoder(VerifiableProperties verifiableProperties) {
        /* This constructor must be present for successful compile. */
    }

    @Override
    public Object fromBytes(byte[] bytes) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.readValue(bytes, Map.class);
        } catch (IOException e) {
            LOGGER.error(String.format("Json processing failed for object: %s", bytes.toString()), e);
        }
        return null;
    }
}

L'entrée pom

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.4.1.3</version>
</dependency>

Définissez l'encodeur par défaut dans la propriété Kafka

properties.put("serializer.class","kafka.serializer.DefaultEncoder");

Le code d'écriture et de lecture est le suivant

byte[] bytes = encoder.toBytes(map);
        KeyedMessage<String, byte[]> message =new KeyedMessage<String, byte[]>(this.topic, bytes);

JsonDecoder decoder = new JsonDecoder(null);
Map map = (Map) decoder.fromBytes(it.next().message());
12
Harvinder Singh