PHP7中Protobuf的安装使用

写这篇文章的缘由是最近在关注RPC框架序列化的一些原理。但是在安装Protobuf的时候,发现网上的教程都太老了,加上目前Protobuf官方已经支持PHP了,不再需要使用第三方插件了。

关于序列化和反序列化

在PRC框架中,数据的传输发生在客户端和服务端,而我们知道基于TCP协议最终传输的是二进制的0/1序列。所以,基于TCP传输协议的RPC服务自然也需要将数据结构转换成二进制,和二进制转换成数据结构的功能。所以,原则上,基于网络的数据传输只能传输二进制表示的字符串

序列化:将数据结构或对象转换成二进制串的过程
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

但是,传输的二进制序列是完全没有意义的,除非有一套解析二进制串的协议。没错,这个协议可以就是目前我们大家熟知的xml,json协议。当然。除了这两者,还有其他的的序列化和反序列化协议。

几种常见的序列化和反序列协议

XML

XML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。XML历史悠久,其1.0版本早在1998年就形成标准,并被广泛使用至今。XML的格式如下

<note>
    <to>George</to>
    <from>John</from>
    <msg>Don't forget the meeting!</msg>
</note>

可以看出这种序列化协议的优点是可读性和易调试行。但是这种协议的缺点也很明显:额外空间开销大,序列化之后的数据量剧增。

JSON

JSON是一种轻量级的数据交换格式。采用完全独立于编程语言的文本格式来存储和表示数据。如果你跟浏览器Web应用打交道的话,那么JSON一定是应用最广泛的,它的数据格式如下

{
    "to":"George",
    "from":"John",
    "msg":"Don't forget the meeting!"
}

这种序列化协议有很大的优势:

  1. 这样表示非常符合工程师对对象的理解,尤其是js工程师
  2. 和xml一样,可读性强
  3. 和xml相比,更加节省空间,解析速度更快

由于天生的Web友好型,JSON自然而然成了AJAX数据传输的标准协议。JSON目前的使用非常广泛,但是,如果数据传输和响应时间有跟苛刻的要求,那么JSON可能性能还是差点。

Protobuf

Google Protocol Buffer( 简称 Protobuf) 是一种更轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC数据交换格式。数据结构定义文件的格式如下:

syntax = "proto3";
package config;
message MailConfig{
   string to = 1;
   string from = 2;
   string msg = 3;
}

然后这个协议文件客户端和服务端都需要用到。这样,网络发送字节就不需要发送和数据无关的字节了。针对上面的数据结构,序列化成json的长度是63,而按照Protobuf协议序列化之后的长度是41。如果数据更多,效果更明显。

Thrift

和Protobuf类似,但是Protobuf只做数据序列化的工作,而Thrift是一套完整的RPC框架解决方案。由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,所以如果你使用Thrift框架,那么其实就使用了Thrift的序列化协议。

Thrift是一套完整的RPC解决方案,里面的协议也是一种基于二进制串的序列化协议,如果想要了解更多,这篇文章写得比较好:https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/

Protobuf的安装使用

json和xml相信大家都比较熟悉,但是在RPC框架中很少使用这两种协议。由于Thrift是一套RPC解决方案,太过庞大,所以我们分析专门解决序列化问题的Protobuf。由于笔者使用的是PHP,所以自然而然使用的是PHP版本的Protobuf。PHP有两种安装模式,第一是composer安装php包的形式,第二种是安装PHP扩展的形式。

PHP和protoc的安装

要安装PHP版本的Protobuf,首先你得有PHP,所以首先安装PHP,笔者使用的PHP7,还需要一个工具protoc,你可以先不用管这个工具是干嘛的,先安装。

工具包版本下载地址
PHP7.1.8http://php.net/get/php-7.1.8.tar.gz/from/a/mirror
proto3.0.0https://github.com/google/protobuf/releases/download/v3.3.0/protobuf-php-3.3.0.zip

最后,笔者安装的PHP版本

[xxx@controller app]$ /usr/bin/php -v
PHP 7.1.8 (cli) (built: Aug 16 2017 03:10:49) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

笔者安装的protoc版本(注意,有可能安装路径不同)

[xxxx@controller app]$ /usr/local/bin/protoc --version
libprotoc 3.3.0

Protobuf的安装

Protobuf的安装我们采用Composer形式进行安装。如果你还没有安装composer工具,运行下面的命令进行安装:

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

查看我们安装的composer版本:

[xxxx@controller app]$ /usr/local/bin/composer --version
Composer version 1.5.1 2017-08-09 16:07:22

准备工作结束。接下来,正式安装Protobuf。

我们首先新建一个文件夹app。然后在app文件夹内新建composer.json文件,文件内容如下:

{
    "require":{
        "google/protobuf": "^3.3"
    }
}

保存之后,在app文件夹下执行composer install安装命令

[xxxx@controller app]$ /usr/local/bin/composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing google/protobuf (v3.4.0): Downloading (100%)    

安装完成之后,如果你看到的app文件夹包含下面文件,表示安装成功

[xxxx@controller app]$ ls
composer.json  composer.lock  vendor

Protobuf的简单例子

上面的步骤安装完之后,我们来看一下一个简单的例子,这个例子很简单,一个角色是Writer,一个角色是Reader。Writer向磁盘中写二进制字符串,Reader要读取二进制字符串中的信息。

书写 .proto 文件

我们知道,二进制序列如果没有协议,根本不知道它表示的是什么,首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf的术语中,结构化数据被称为 Message。我们这里定义一个mail的proto文件mail.proto,这个文件还是放在我们的app目录下, 文件内容如下:

syntax = "proto3";
package mail;
message MailConfig{
   string to = 1;
   string from = 2;
   string msg = 3;
}

其中,syntax表示我们使用的是proto3语法规则。packge的名字叫做mail,然后定义了一个名字为MailConfig的消息。这个消息有3个成员,string类型的to,string类型的from,string类型的msg。

编译 .proto 文件

还记得我们下载安装的protoc工具吗,这个玩意就是拿来编译.proto文件的,它可以将.proto文件编译成很多种目标语言,java,python,php等。这里我们需要使用这个工具,将.proto文件生成目标PHP语言。

/usr/local/bin/protoc --php_out=. mail.proto

--php-out表示生成目标语言存放位置,我们就放在当前目录下,也就是app文件夹下。如果生成目标语言成功,那么app文件夹下的目录如下:

[xxxx@controller app]# ls
composer.json  composer.lock  GPBMetadata  Mail  mail.proto  vendor

编写测试

在app中编写test.php,文件内容如下:

<?php
    require_once('vendor/autoload.php');
    require_once('GPBMetadata/Mail.php');
    require_once('Mail/MailConfig.php');

    /**
    * Writer写数据,Protobuf抽象成调用相关set函数即可
    */
    $foo = new \Mail\MailConfig();  
    $foo->setTo("George");  
    $foo->setFrom("John");  
    $foo->setMsg("Don't forget the meeting!");  
  
    $packed = $foo->serializeToString();//这里你也可以选择serializeToJsonString序列化成JSON  
  
    //Reader读数据,Protobuf抽象成调用相关get函数即可
    $res = new \Mail\MailConfig();
    $res->mergeFromString($packed);  
    $jsonArr = [
        "to"=> $res->getTo(),
        "from"=> $res->getFrom(),
        "msg"=> $res->getMsg(),
    ];
    var_dump($jsonArr);

最终程序的输出:

[xxxx@controller app]# php test.php 
array(3) {
  ["to"]=>
  string(6) "George"
  ["from"]=>
  string(4) "John"
  ["msg"]=>
  string(25) "Don't forget the meeting!"
}

这就是一个典型的序列化和反序列化的例子,试想一下,如果我们将序列化好的二进制串通过网络发送到另一端,而另一端再使用同样的.proto文件生成的目标语言解析。这将变得比json更加的高效。因为我们可以传输更少的字节,反序列化速度也非常快。

后续

到这里,肯定有人觉得效率还是不够高,那么没关系,protobuf有C语言扩展版的实现,如果你对效率有极致的要求,那么强烈建议使用C语言扩展版的protobuf。可以参考官方安装方式:https://github.com/google/protobuf/tree/master/php