Protobuf
星期一, 3月 17, 2025 | 4分钟阅读

关于Protobuf的学习。
简介
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。简单的理解:Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。
安装
上传、解压。
[root@node01 ~]# tar -zxvf protobuf-2.6.1.tar.gz
[root@node01 ~]# rm protobuf-2.6.1.tar.gz -rf
[root@node01 ~]# mkdir -p /opt/yjx/protobuf-2.6.1
配置、编译、安装。
[root@node01 ~]# yum -y install gcc-c++
[root@node01 ~]# cd protobuf-2.6.1
[root@node01 protobuf-2.6.1]# ./configure --prefix=/opt/yjx/protobuf-2.6.1
[root@node01 protobuf-2.6.1]# make && make install
使用
- 先编写 Protobuf 语言代码,设置压缩的字段和范围;
- 执行 bin 目录下的 proto 程序,将 Protobuf 的程序编译为 Java 程序;
- 使用 HBase Java API 时(加载或者读取),配合 Protobuf 里边的方法一起使用。
编码
新建 vim /root/PhoneRecordProtos.proto
文件,新增以下内容:
package com.yjxxt.hbase.protobuf.util;
option java_outer_classname = "PhoneRecordProtos";
// 每条消息
message PhoneRecord
{
required string otherphone = 1;
optional int32 time = 2;
optional int64 date = 3;
optional string type = 4;
}
// 每日消息
message PhoneRecordDay
{
repeated PhoneRecord phoneRecord = 1;
}
// 每月消息
message PhoneRecordMonth
{
repeated PhoneRecordDay phoneRecordDay = 1;
}
执行
cd 到 protobuf 的 bin 目录下 cd /opt/yjx/protobuf-2.6.1/bin ,执行以下命令:
./protoc --java_out=/root --proto_path=/root/ /root/PhoneRecordProtos.proto
将执行后生成的 Java 文件拉取到本地并拷贝至项目。
使用
使用 Protobuf 加载数据和查询数据。
package com.yjxxt.protobuf;
import com.yjxxt.hbase.protobuf.util.PhoneRecordProtos;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.Date;
import java.util.List;
@DisplayName("Protobuf 测试类")
public class PhoneRecordProtobufTest {
/**
* HBase 的管理对象
*/
private Admin admin;
/**
* 数据库连接
*/
private Connection connection;
/**
* 表对象
*/
private Table table;
/**
* 表名
*/
private final static String TABLE_NAME = "t_phone_protobuf";
/**
* 列族
*/
private final static String COLUMN_FAMILY_NAME = "info";
@DisplayName("创建数据库连接并初始化管理类和数据库表")
@BeforeEach
public void init() throws IOException {
// 初始化 HBase 配置类
Configuration configuration = HBaseConfiguration.create();
// 创建数据库连接
connection = ConnectionFactory.createConnection(configuration);
// 初始化 HBase 管理类
admin = connection.getAdmin();
// 获取数据库表
table = connection.getTable(TableName.valueOf(TABLE_NAME));
}
@DisplayName("释放 HBase 资源")
@AfterEach
public void destory() throws IOException {
if (table != null) {
table.close();
}
if (admin != null) {
admin.close();
}
if (connection != null) {
connection.close();
}
}
@DisplayName("创建表")
@Test
public void testCreateTable() throws IOException {
// 判断表是否存在
if (admin.tableExists(TableName.valueOf(TABLE_NAME))) {
System.out.println(TABLE_NAME + " 表已存在");
return;
}
// 构建表
TableDescriptorBuilder table = TableDescriptorBuilder
.newBuilder(TableName.valueOf(TABLE_NAME));
// 构建列族
ColumnFamilyDescriptor info = ColumnFamilyDescriptorBuilder
.newBuilder(Bytes.toBytes(COLUMN_FAMILY_NAME))
.build();
// 设置列族
table.setColumnFamily(info);
// 创建表
admin.createTable(table.build());
}
@DisplayName("配合 Protobuf 压缩数据")
@Test
public void puts() throws IOException {
// 创建插入对象
Put put = new Put("18001128288_20220901000000000".getBytes());
// 配合 Protobuf 压缩数据
PhoneRecordProtos.PhoneRecordDay.Builder phoneRecordDay =
PhoneRecordProtos.PhoneRecordDay.newBuilder();
// 插入100条通话记录
for (int i = 100; i < 200; i++) {
// 配合 Protobuf 创建每一条数据对象
PhoneRecordProtos.PhoneRecord.Builder phoneRecord =
PhoneRecordProtos.PhoneRecord.newBuilder();
phoneRecord.setOtherphone("13812345" + i);
phoneRecord.setTime(i);
phoneRecord.setDate(new Date().getTime());
phoneRecord.setType(String.valueOf(i % 2));
// 将对象添加到每日记录
phoneRecordDay.addPhoneRecord(phoneRecord);
}
// 将数据写出
put.addColumn(Bytes.toBytes(COLUMN_FAMILY_NAME), Bytes.toBytes("day"),phoneRecordDay.build().toByteArray());
// 插入数据
table.put(put);
}
@DisplayName("配合 Protobuf 获取压缩数据")
@Test
public void gets() throws IOException {
Get get = new Get(Bytes.toBytes("18001128288_20220901000000000"));
Result result = table.get(get);
byte[] value = result.getValue(Bytes.toBytes(COLUMN_FAMILY_NAME), Bytes.toBytes("day"));
// 配合 Protobuf 解析压缩数据
PhoneRecordProtos.PhoneRecordDay phoneRecordDay =
PhoneRecordProtos.PhoneRecordDay.parseFrom(value);
List<PhoneRecordProtos.PhoneRecord> phoneRecordList = phoneRecordDay.getPhoneRecordList();
for (PhoneRecordProtos.PhoneRecord phoneRecord : phoneRecordList) {
long date = phoneRecord.getDate();
String otherPhone = phoneRecord.getOtherphone();
int time = phoneRecord.getTime();
System.out.println(otherPhone + " - " + time + " - " + date);
}
}
}
优缺点
Protobuf 的优点
- 更小更快更简单:Protobuf 犹如 XML,不过它更小、更快、也更简单;
- 自定义数据结构:Protobuf 支持自定义数据结构:
- 使用代码生成器生成的代码来读写这个数据结构
- 可以在无需重新部署程序的情况下更新数据结构
- 只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写
- 向后兼容:不必破坏已经部署的、依靠老数据格式的程序就可以实现对数据结构进行升级。这样你的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题;
- 语义清晰:Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作);
- 简单易学:使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。
Protobuf 的缺点
Protbuf 与 XML 相比也有不足之处:
- 功能简单:无法用来表示复杂的概念;
- 通用性差:XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多;
- 不适用于对基于文本的标记文档(如 HTML)建模,因为文本并不适合用来描述数据结构;
- 由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出 Protobuf 的任何内容。