kafka2.0-使用protobuf實現序列化_08

上一篇文章中講了json和jdk的序列化方式,這裏講使用protobuf類序列化

protobuf的介紹

Google Protocol Buffer( 簡稱 Protobuf) 是 Google
公司內部的混合語言數據標準,目前已經正在使用的有超過 48,162 種報文格式定義和超過 12,183 個 .proto 文件。他們用於
RPC 系統和持續數據存儲系統。

Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化。它很適合做數據存儲或 RPC
數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python
三種語言的 API。
更詳細的信息可以查看下面這篇文章,我也是從那邊摘錄過的
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html

使用protobuf生成java文件

如果你已經熟悉了protobuf的使用可以跳過這部分。
首先下載一個window版本的protobuf,我下載的是protoc-2.5.0-win32.zip的這個版本,下載鏈接如下:
https://github.com/protocolbuffers/protobuf/releases/download/v2.5.0/protoc-2.5.0-win32.zip

我下載的這個版本比較舊,因爲我之前寫的proto腳本是protobuf2的,下面的這個鏈接裏可以下載最新的版本。
https://github.com/protocolbuffers/protobuf/releases

下載完成之後,解壓,你會發現有個protoc.exe可執行文件,然後新建一個proto文件夾
這裏寫圖片描述

進入 proto文件夾,新建User.proto文件。
這裏寫圖片描述
然後用記事本打開User.proto,然後輸入一下代碼

syntax = "proto2";

package kafka;
option java_package = "com.yang.kafka.protobuf";
option java_outer_classname = "UserProto";

message User{
    required int64 id = 1;
    required string name = 2;
    optional string email = 3;
    required int32 sex = 4;
}

然後保存。打開cmd 進入到protoc.exe所在的目錄,然後輸入一下命令
protoc -I=E:\protobuf\proto --java_out=E:\ E:\protobuf\proto\User.proto,如下圖:
生成java文件
第一個地址是proto文件所在的目錄,第二個地址是輸出的java文件的目錄,第三個地址是proto文件的路徑
命令執行成功後,你會發現在E:\下生成了對應的java文件,將他copy到你的java工程中(java項目中的包名要與你之前的包名相同,否則copy進去這個java文件會報錯)。
這裏寫圖片描述

kafka中使用protobuf實現序列化

首先定義一個接口

public interface Protobufable {

    //將對象轉爲字節數組
    public byte[] encode();

}

然後User 實現這個接口,這裏可以不實現Serializable接口,User 類代碼如下:

public class User implements Serializable,Protobufable{
    private static final long serialVersionUID = 468062760765055608L;

    private Long id;

    private String name;

    private String email;
    /** {0:男,1:女} **/
    private Integer sex;

    public User() {}

    public User(Long id, String name, String email, Integer sex) {
        super();
        this.id = id;
        this.name = name;
        this.email = email;
        this.sex = sex;
    }

    /***
     * 將byte解析爲User對象
     * @param bytes
     */
    public User(byte[] bytes) {
        try {
            //將字節數組轉換爲UserProto.User對象
            UserProto.User user = UserProto.User.parseFrom(bytes);
            //UserProto.User對象轉化爲自己的User對象
            this.id = user.getId();
            this.name = user.getName();
            this.email = user.getEmail();
            this.sex = user.getSex();
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }

    /** 編碼 */
    @Override
    public byte[] encode() {
        UserProto.User.Builder builder = UserProto.User.newBuilder();
        builder.setId(id);
        builder.setName(name);
        builder.setEmail(email);
        builder.setSex(sex);
        return builder.build().toByteArray();
    }

    @Override
    public String toString() {
        return "[ID:" + id + ", 姓名:" + name + ", 性別:" + (sex==0?"男":"女") + ", 郵箱:" + email + "]";
    }

    /********************** getter & setter******************************/
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    public Integer getSex() { return sex; }
    public void setSex(Integer sex) { this.sex = sex; }
    /********************** getter & setter******************************/
}

再然後,實現一個ProtobufSerializer 的序列化器,調用的實際上就是user.encode(),代碼如下:

/**
 * protobuf序列化方式實現kafka消息的的序列化
 */
public class ProtobufSerializer implements Serializer<Protobufable>{

    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {}

    @Override
    public byte[] serialize(String topic, Protobufable data) {
        return data.encode();
    }

    @Override
    public void close() {}
}

生產者代碼:

/**
 * 生產者-使用Protobuf序列化
 */
public class ProtobufSerializerProducer {

    public static final String TOPIC_NAME = "producer-0"; 
    private  static Properties props = new Properties();

    static{
         props.put("bootstrap.servers", "192.168.1.3:9092,192.168.1.128:9092,192.168.1.130:9092");
         props.put("acks", "all");
         props.put("retries", 0);
         props.put("batch.size", 16384);
         props.put("linger.ms", 1);
         props.put("buffer.memory", 33554432);
         props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
         props.put("value.serializer", "com.yang.kafka.serialization.ProtobufSerializer");
    }

    public static void main(String[] args) {
         Producer<String, User> producer = new KafkaProducer<>(props);

         User user = new User(101L,"kafka","[email protected]",1);
         producer.send(new ProducerRecord<String, User>(TOPIC_NAME, Long.toString(user.getId()), user));

         producer.close();
    }
}

消費者代碼如下,這裏對value.deserializer換成了ByteArrayDeserializer,其實就是返回的字節數組

/**
 * 消費者-使用Protobuf反序列化
 */
public class ProtobufDeserializerConsumer {
    private  static Properties props = new Properties();

    private static boolean isClose = false;

    static{
         props.put("bootstrap.servers", "192.168.1.3:9092,192.168.1.128:9092,192.168.1.130:9092");
         props.put("group.id", "test");
         props.put("enable.auto.commit", "true");
         props.put("auto.commit.interval.ms", "1000");
         props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
         props.put("value.deserializer", "org.apache.kafka.common.serialization.ByteArrayDeserializer");
    }

    public  static void main(String args[]){
         KafkaConsumer<String, byte[]> consumer = new KafkaConsumer<>(props);
         consumer.subscribe(Arrays.asList(JSONSerializerProducer.TOPIC_NAME));

         while (!isClose) {
             ConsumerRecords<String, byte[]> records = consumer.poll(Duration.ofMillis(100));
             for (ConsumerRecord<String, byte[]> record : records)
                 System.out.printf("key = %s, value = %s%n", record.key(), new User(record.value()));
         }

         consumer.close();
    }
}

消費者接收到的消息一上一篇文章中其實是一樣的:
這裏寫圖片描述

再通過profobuf生成了java實體類之後,仔細的看User類,其實UserProto中生成了很多的工具類,可以實現字節數組與對象的轉換。

順便做了一個小實驗,對於User user = new User(101L,"kafka","[email protected]",1);這個對象採用不同的序列化方式,輸出其字節碼長度,如下:

key = jdk, 字節碼長度 = 292
key = json, 字節碼長度 = 64
key = protobuf, 字節碼長度 = 33

可以看到相同的內容,JDK的序列化方式要明顯大很多,這無疑多佔用了很多kafka集羣的資源。當然只從上面這個無法充分的說明孰優孰劣。
這裏有一篇我覺得寫的很好的文章,是關於序列化效率的:http://www.sohu.com/a/136487507_505779

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章