上一篇文章中講了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
,如下圖:
第一個地址是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