好久之前做的业务了,网上涉及到 laravel 使用 protobuf 的文章少的可怜,自己看了很多相关的文章,总结出来的用法,应该会有不少人需要
一、protobuf 简单介绍
Protobuf 是 Google 公司内部的混合语言数据标准,
是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
二、在 laravel 中封装使用 protobuf
PHP详解Protobuf的使用,工作实际业务是对接巨量引擎的 API 用到了,遇到了一些坑,估计大家也都会遇到
1.Linux安装 protobuf 命令
安装此命令是为了能够解析指令,生成对应的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //获取 3.x 版本 wget https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protobuf-php-3.12.0.tar.gz //解压代码 tar zxvf protobuf-php-3.12.0.tar.gz //进入目录 cd protobuf-php-3.12.0 // 检查环境-编译安装 ./configure --prefix=/usr/local/protobuf sudo make sudo make install // 设置全局 export PATH=/usr/local/protobuf/bin:$PATH // 测试是否安装成功 protoc --version |
2. composer 安装包依赖
切换 composer 仓库源:
1 | composer config repo.packagist composer https://mirrors.aliyun.com/composer/ |
安装包
1 | composer require google/protobuf:^3.3 |
因为 PHP 的composer 只支持了 protobuf 3,所以我们没办法去使用 version 2,因此,在对接 version 2 的验证时,会有一些兼容的问题。下面我会说明,两个版本之间的差别可以简单看一下这篇文章:
protobuf 2 和 3的区别
3.laravel 生成所需文件
我基于巨量引擎的对接API为例,在上传人群包文件时要求我们对文件进行序列化操作,如下图所示:
这是头条提供的上传格式 (version 2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; IMEI_MD5 = 4; IDFA_MD5 = 5; MOBILE_HASH_SHA256 = 6; OAID = 7; OAID_MD5 = 8; } } |
因为给的是 protobuff 2 的格式,所以我们需要改成 protobuff 3 的,修改后的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | syntax = "proto3"; package toutiao.dmp; option java_outer_classname = "DmpDataProto"; message DmpData { //上传文件每行一个base64编码的字符串,每个字符串包含一个完整的DmpData消息二进制字节串 repeated IdItem idList = 1; // 每行数据包含的idList大小不能超过10000 } message IdItem { uint32 timestamp = 1; //若不设置,默认以上传文件的创建时间为此条记录的创建时间 string id = 3; //根据dataType字段的类型,放置对应类型的id的字符串,需要小写 string tags = 4; //标识此id的业务标签字符串 DataType dataType = 2; //指定此id的类型,如IMEI、IDFA等 enum DataType { IMEI = 0; IDFA = 1; UID = 2; IMEI_MD5 = 4; IDFA_MD5 = 5; MOBILE_HASH_SHA256 = 6; OAID = 7; } } |
在根目录下(任意目录下其实都可以)新建以下文件夹,用于存放proto 文件 和 生成的文件
在上级目录执行下列语句(就是 在 protobuf的上级目录):
1 | protoc --php_out="protobuf/compile" "protobuf/protos/DmpDataProto.proto" |
生成的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 | ├── compile │ ├── GPBMetadata │ │ └── Protobuf │ │ └── Protos │ │ └── DmpDataProto.php │ └── Toutiao │ └── Dmp │ ├── DmpData.php │ ├── IdItem_DataType.php │ └── IdItem.php └── protos └── DmpDataProto.proto |
4.自动加载生成的文件
如果框架没有加载我们自定义的目录文件,那么我们需要手动配置autoload, 在 composer.json 中的 autoload 下的 psr4 加上两个命名空间:
1 2 | "Toutiao\": "protobuf/compile/Toutiao", "GPBMetadata\\Protobuf\\Protos\": "protobuf/compile/GPBMetadata/Protobuf/Protos" |
5.编写代码使用
简单写一个序列化的代码,反序列化的代码,你可以按需修改,需要注意的是,如果你做的也刚好是这个业务的话,因为版本不兼容的问题,我们用v ersion 3 序列化的文件,version 2 是解不出来的,通过实验发现每次都是少了一个前缀,因此我在下面的代码手动带上了这个前缀,头条才让我过。如果你不需要可以去掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * 获取序列化的一行 * @param $id * @return string */ public function getFormatLine($line) { $idItem = new IdItem(); $idItem->setDataType(IdItem_DataType::IMEI); $idItem->setId(strtolower($line)); $idItem->setTags('IMEI'); $binaryString = $idItem->serializeToString(); // 手动拼前缀,不需要可以去掉 $prefix = "\n\x19\x10\x00"; $binaryString = $prefix . $binaryString; return $binaryString; } /** * 获取反序列的一行 * @param $line * @return string * @throws \Exception */ public function decodeOneLine($line) { $item = new IdItem(); $item->mergeFromString($line); return $item->getId(); } |