工作需要 Protobuf 进行序列化传输,这里记录一下。
Protobuf 是google开发的一种用于高效存储和读取结构化数据的工具。类似于Json,xml等用来重复的记录结构化的数据,Protobuf 序列化和反序列化的性能非常高,而且压缩比Json等高很多。
功能主要是使用 Protobuf 生成一个.proto 文件,再编译成相应语言代码,调用即可。
Centos7.3,Scala + Java 环境。这里简单用Scala实现以下,事实上Scala 有 ScalaPB,是 Protocol buffer 的编译器插件,可以为 Protobuf 自动生成Scala case类、解析器和序列化。
安装
最近版本3.9.2 下载源码 Source code
查看删除旧版本位置
查看版本
cd /usr/local/protobuf-3.9.2/bin
./protoc --version
删除
whereis protoc
# 如果存在删除即可
rm /usr/local/bin/protoc
安装依赖库
# 用到的库
yum install -y unzip autoconf automake libtool make g++ gcc-c++ glibc-headers
解压到当前目录
unzip protobuf-3.9.2.zip
tar -zxvf protobuf-3.9.2.tar.gz
编译
cd protobuf-3.9.2
./autogen.sh
./configure prefix=/usr/local/protobuf-3.9.2 # 可以不指定克隆路径 prefix=
make
make check
make install
使用
创建声明文件,如头条的dmp数据
vim toutiao_dmp.proto
package toutiao.dmp;
option java_outer_classname = "DmpDataProto";
message DmpData { //上传文件每行一个base64编码的字符串,每个字符串包含一个完整的DmpData消息二进制字节串
repeated IdItem idList = 1; // 每行数据包含的idList大小不能超过10000
}
message IdItem {
optional uint32 timestamp = 1; //若不设置,默认以上传文件的创建时间为此条记录的创建时间
required DataType dataType = 2; //指定此id的类型,如IMEI、IDFA等
required string id = 3; //根据dataType字段的类型,放置对应类型的id的字符串,需要小写
repeated string tags = 4; //标识此id的业务标签字符串
enum DataType {
IMEI = 0;
IDFA = 1;
UID = 2;
MOBILE = 3;
IMEI_MD5 = 4;
IDFA_MD5 = 5;
MOBILE_HASH_SHA256 = 6;
}
}
生成 java 文件
./protoc toutiao_dmp.proto --java_out=./
# 生成的文件位置, protobuf-3.9.2/toutiao/dmp/DmpDataProto.java
测试
object MakeToutiaoDmp {
def main(args: Array[String]): Unit = {
genereate_valid_file()
}
def genereate_valid_file(): Unit = {
// 使用builder构建一个对象
val dmp_data = DmpData.newBuilder
val id_item1 = dmp_data.addIdListBuilder()
.setDataType(IdItem.DataType.IMEI)
.setId("356145080566857")
.addTags("信用卡")
.addTags("理财")
.setTimestamp(System.currentTimeMillis().toInt)
val id_item2 = dmp_data.addIdListBuilder()
.setDataType(IdItem.DataType.IDFA)
.setId("1E2DFA89-496A-47FD-9941-DF1FC4E6484A")
.addTags("黄金")
.addTags("理财")
// build() 结束builder,返回对象
val binary_string = dmp_data.build().toByteArray
val result_string = Base64.decodeBase64(binary_string)
// 测试 1
// 序列化
// val buff = id_item1.build().toByteArray
// val in = new ByteArrayInputStream(buff)
// 反序列化
// val item = IdItem.parseFrom(in)
// println(item.getDataType, item.getId, item.getTagsList,item.getTimestamp)
// (IMEI,356145080566857,[信用卡, 理财],1886284553)
// 测试 2
// 序列化
val out = new ByteArrayOutputStream
id_item2.build().writeTo(out)
val buff = out.toByteArray
// 反序列化
val in = new ByteArrayInputStream(buff)
val id_item3 = IdItem.parseFrom(in)
println(id_item3.getDataType,id_item3.getId,id_item3.getTagsList)
// (IDFA,1E2DFA89-496A-47FD-9941-DF1FC4E6484A,[黄金, 理财])
// 输出到文件
val output = new FileOutputStream("d:\\data\\target")
output.write(result_string)
output.write('\n')
output.close()
}
}
调用
/**
* 序列化
*/
def validateAndGenerateFile(line: String): Array[Byte] = {
val strings = line.split(",")
val dmpDataBuilder = DmpData.newBuilder()
dmpDataBuilder.addIdListBuilder()
.setDataType(IdItem.DataType.valueOf(strings(0).toUpperCase))
.setId(strings(1))
.build()
val byteArray = dmpDataBuilder.build().toByteArray
Base64.encodeBase64(byteArray)
}
Builder 和 Message
Protobuf 生成的java类中,包含两种内部类:Builder和Msg。区别:
Builder 用来构建类,提供类操作的API,set、get等方法
Msg 用来获取数据、序列化、反序列化,没有set方法
一般会先Builder构建Msg,再使用 Msg 序列化成字节流。