web-dev-qa-db-fra.com

Comment encoder / décoder les messages Kafka en utilisant l'encodeur binaire Avro?

J'essaie d'utiliser Avro pour les messages lus/écrits dans Kafka. Quelqu'un at-il un exemple d'utilisation de l'encodeur binaire Avro pour encoder/décoder des données qui seront placées dans une file d'attente de messages?

J'ai plus besoin de la partie Avro que de la partie Kafka. Ou, peut-être que je devrais chercher une solution différente? Fondamentalement, j'essaie de trouver une solution plus efficace pour JSON en ce qui concerne l'espace. Avro vient d'être mentionné car il peut être plus compact que JSON.

27
blockcipher

Je me suis finalement souvenu de demander la liste de diffusion Kafka et j'ai obtenu la réponse suivante, ce qui fonctionnait parfaitement.

Oui, vous pouvez envoyer des messages sous forme de tableaux d'octets. Si vous regardez le constructeur de la classe Message, vous verrez -

def this (octets: Array [Byte])

Maintenant, en regardant l'API Producer send () -

envoi par défaut (producteurData: ProducerData [K, V] *)

Vous pouvez définir V pour être de type Message et K pour ce que vous voulez que votre clé soit. Si vous ne vous souciez pas du partitionnement à l'aide d'une clé, définissez-le également sur Type de message.

Merci, Neha

11
blockcipher

Ceci est un exemple de base. Je ne l'ai pas essayé avec plusieurs partitions/sujets.

// Exemple de code producteur

import org.Apache.avro.Schema;
import org.Apache.avro.generic.GenericData;
import org.Apache.avro.generic.GenericRecord;
import org.Apache.avro.io.*;
import org.Apache.avro.specific.SpecificDatumReader;
import org.Apache.avro.specific.SpecificDatumWriter;
import org.Apache.commons.codec.DecoderException;
import org.Apache.commons.codec.binary.Hex;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
import Java.io.ByteArrayOutputStream;
import Java.io.File;
import Java.io.IOException;
import Java.nio.charset.Charset;
import Java.util.Properties;


public class ProducerTest {

    void producer(Schema schema) throws IOException {

        Properties props = new Properties();
        props.put("metadata.broker.list", "0:9092");
        props.put("serializer.class", "kafka.serializer.DefaultEncoder");
        props.put("request.required.acks", "1");
        ProducerConfig config = new ProducerConfig(props);
        Producer<String, byte[]> producer = new Producer<String, byte[]>(config);
        GenericRecord payload1 = new GenericData.Record(schema);
        //Step2 : Put data in that genericrecord object
        payload1.put("desc", "'testdata'");
        //payload1.put("name", "अasa");
        payload1.put("name", "dbevent1");
        payload1.put("id", 111);
        System.out.println("Original Message : "+ payload1);
        //Step3 : Serialize the object to a bytearray
        DatumWriter<GenericRecord>writer = new SpecificDatumWriter<GenericRecord>(schema);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(out, null);
        writer.write(payload1, encoder);
        encoder.flush();
        out.close();

        byte[] serializedBytes = out.toByteArray();
        System.out.println("Sending message in bytes : " + serializedBytes);
        //String serializedHex = Hex.encodeHexString(serializedBytes);
        //System.out.println("Serialized Hex String : " + serializedHex);
        KeyedMessage<String, byte[]> message = new KeyedMessage<String, byte[]>("page_views", serializedBytes);
        producer.send(message);
        producer.close();

    }


    public static void main(String[] args) throws IOException, DecoderException {
        ProducerTest test = new ProducerTest();
        Schema schema = new Schema.Parser().parse(new File("src/test_schema.avsc"));
        test.producer(schema);
    }
}

// Exemple de code consommateur

Partie 1: Code de groupe de consommateurs: car vous pouvez avoir plus de plusieurs consommateurs pour plusieurs partitions/sujets.

import kafka.consumer.ConsumerConfig;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;

import Java.util.HashMap;
import Java.util.List;
import Java.util.Map;
import Java.util.Properties;
import Java.util.concurrent.Executor;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
import Java.util.concurrent.TimeUnit;

/**
 * Created by  on 9/1/15.
 */
public class ConsumerGroupExample {
   private final ConsumerConnector consumer;
   private final String topic;
   private ExecutorService executor;

   public ConsumerGroupExample(String a_zookeeper, String a_groupId, String a_topic){
      consumer = kafka.consumer.Consumer.createJavaConsumerConnector(
              createConsumerConfig(a_zookeeper, a_groupId));
      this.topic = a_topic;
   }

   private static ConsumerConfig createConsumerConfig(String a_zookeeper, String a_groupId){
       Properties props = new Properties();
       props.put("zookeeper.connect", a_zookeeper);
       props.put("group.id", a_groupId);
       props.put("zookeeper.session.timeout.ms", "400");
       props.put("zookeeper.sync.time.ms", "200");
       props.put("auto.commit.interval.ms", "1000");

       return new ConsumerConfig(props);
   }

    public void shutdown(){
         if (consumer!=null) consumer.shutdown();
        if (executor!=null) executor.shutdown();
        System.out.println("Timed out waiting for consumer threads to shut down, exiting uncleanly");
        try{
          if(!executor.awaitTermination(5000, TimeUnit.MILLISECONDS)){

          }
        }catch(InterruptedException e){
            System.out.println("Interrupted");
        }

    }


    public void run(int a_numThreads){
        //Make a map of topic as key and no. of threads for that topic
        Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(topic, new Integer(a_numThreads));
        //Create message streams for each topic
        Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
        List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);

        //initialize thread pool
        executor = Executors.newFixedThreadPool(a_numThreads);
        //start consuming from thread
        int threadNumber = 0;
        for (final KafkaStream stream : streams) {
            executor.submit(new ConsumerTest(stream, threadNumber));
            threadNumber++;
        }
    }
    public static void main(String[] args) {
        String zooKeeper = args[0];
        String groupId = args[1];
        String topic = args[2];
        int threads = Integer.parseInt(args[3]);

        ConsumerGroupExample example = new ConsumerGroupExample(zooKeeper, groupId, topic);
        example.run(threads);

        try {
            Thread.sleep(10000);
        } catch (InterruptedException ie) {

        }
        example.shutdown();
    }


}

Partie 2: Consommateur individuel qui consomme réellement les messages.

import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.message.MessageAndMetadata;
import org.Apache.avro.Schema;
import org.Apache.avro.generic.GenericRecord;
import org.Apache.avro.generic.IndexedRecord;
import org.Apache.avro.io.DatumReader;
import org.Apache.avro.io.Decoder;
import org.Apache.avro.io.DecoderFactory;
import org.Apache.avro.specific.SpecificDatumReader;
import org.Apache.commons.codec.binary.Hex;

import Java.io.File;
import Java.io.IOException;

public class ConsumerTest implements Runnable{

    private KafkaStream m_stream;
    private int m_threadNumber;

    public ConsumerTest(KafkaStream a_stream, int a_threadNumber) {
        m_threadNumber = a_threadNumber;
        m_stream = a_stream;
    }

    public void run(){
        ConsumerIterator<byte[], byte[]>it = m_stream.iterator();
        while(it.hasNext())
        {
            try {
                //System.out.println("Encoded Message received : " + message_received);
                //byte[] input = Hex.decodeHex(it.next().message().toString().toCharArray());
                //System.out.println("Deserializied Byte array : " + input);
                byte[] received_message = it.next().message();
                System.out.println(received_message);
                Schema schema = null;
                schema = new Schema.Parser().parse(new File("src/test_schema.avsc"));
                DatumReader<GenericRecord> reader = new SpecificDatumReader<GenericRecord>(schema);
                Decoder decoder = DecoderFactory.get().binaryDecoder(received_message, null);
                GenericRecord payload2 = null;
                payload2 = reader.read(null, decoder);
                System.out.println("Message received : " + payload2);
            }catch (Exception e) {
                e.printStackTrace();
                System.out.println(e);
            }
        }

    }


}

Testez le schéma AVRO:

{
    "namespace": "xyz.test",
     "type": "record",
     "name": "payload",
     "fields":[
         {
            "name": "name", "type": "string"
         },
         {
            "name": "id",  "type": ["int", "null"]
         },
         {
            "name": "desc", "type": ["string", "null"]
         }
     ]
}

Les choses importantes à noter sont:

  1. Vous aurez besoin du standard kafka et des pots avro pour exécuter ce code hors de la boîte.

  2. Est très important props.put ("serializer.class", "kafka.serializer.DefaultEncoder"); Dont use stringEncoder as that wont fonctionne si vous envoyez un tableau d'octets en tant que message.

  3. Vous pouvez convertir l'octet [] en une chaîne hexadécimale et l'envoyer et, sur le consommateur, reconvertir la chaîne hexadécimale en octet [], puis au message d'origine.

  4. Exécutez le gardien de zoo et le courtier comme mentionné ici: - http://kafka.Apache.org/documentation.html#quickstart et créez un sujet appelé "pages_vues" ou ce que vous voulez.

  5. Exécutez ProducerTest.Java, puis ConsumerGroupExample.Java et voyez les données avro produites et consommées.

19
ramu

Si vous souhaitez obtenir un tableau d'octets à partir d'un message Avro (la partie kafka est déjà répondue), utilisez l'encodeur binaire:

    GenericDatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema); 
    ByteArrayOutputStream os = new ByteArrayOutputStream(); 
    try {
        Encoder e = EncoderFactory.get().binaryEncoder(os, null); 
        writer.write(record, e); 
        e.flush(); 
        byte[] byteData = os.toByteArray(); 
    } finally {
        os.close(); 
    }
9
Will Sargent

Réponse mise à jour.

Kafka possède un sérialiseur/désérialiseur Avro avec des coordonnées Maven (formatées SBT):

  "io.confluent" % "kafka-avro-serializer" % "3.0.0"

Vous passez une instance de KafkaAvroSerializer dans le constructeur KafkaProducer.

Ensuite, vous pouvez créer des instances Avro GenericRecord et les utiliser comme valeurs dans les instances Kafka ProducerRecord que vous pouvez envoyer avec KafkaProducer.

Du côté Kafka consommateur, vous utilisez KafkaAvroDeserializer et KafkaConsumer.

3
clay

Au lieu d'Avro, vous pouvez également simplement envisager de compresser les données; soit avec gzip (bonne compression, CPU plus élevé) ou LZF ou Snappy (compression beaucoup plus rapide et un peu plus lente).

Ou bien il y a aussi Smile binary JSON , supporté dans Java par Jackson (avec cette extension ): c'est un format binaire compact, et beaucoup plus facile à utiliser qu'Avro:

ObjectMapper mapper = new ObjectMapper(new SmileFactory());
byte[] serialized = mapper.writeValueAsBytes(pojo);
// or back
SomeType pojo = mapper.readValue(serialized, SomeType.class);

essentiellement le même code qu'avec JSON, sauf pour passer une usine de formats différents. Du point de vue de la taille des données, le fait que Smile ou Avro soit plus compact dépend des détails du cas d'utilisation; mais les deux sont plus compacts que JSON.

L'avantage est que cela fonctionne rapidement avec JSON et Smile, avec le même code, en utilisant uniquement des POJO. Par rapport à Avro qui nécessite soit la génération de code, soit beaucoup de code manuel pour emballer et décompresser GenericRecords.

2
StaxMan