GPB(Google Protobuf)技术总结

1. Gpb概述

在某电信项目中接触到GPB,用于和合作方的数据交换。 在大数据领域使用的非常多。

GPB的全称为Google Protocol Buffer,是谷歌公司用于在大数据存储及交换方面的一个开源的协议和开发库。优势在于定义好数据类型和格式,两方都可以非常高效的写入和读取,并且存储额外开销非常小。

通常我们在小型的数据交换,使用json和xml进行封装,但遇到大量数据时,比如每秒收集和传输1M甚至更多时,json等有很多的冗余数据,占用很多硬盘和带宽资源,这时GPB的优势就显现出来。

GPB是一种高效的结构化数据存储格式,可以将结构化数据序列化,即将数据保存在文件中,这在数据格式与编程语言无关,可以用于通讯协议和数据存储。当然GPB也有一定的劣势,例如其是二进制存储而不是文本存储,其开发和调试都需要工具来支持,在小量数据时,还是建议使用json等来交换数据。

2, 典型案例

假定我们有这样一个需求,我们为sina公司开发一个日志收集及存储系统,将所有的访问日志归档存储,假定每秒有10万用户访问sina,那么我们将有10万条记录需要收集,日志将访问,平均每条记录150字节将会10万*150=15M/s,即每秒收集15M的数据。假定我们收集的数据包括以下几部分

源IP,源端口,使用的UA代理,目标URL, 访问时间。

通常分为客户端和服务器,客户端收集数据然后发给服务器,因此我们定义好数据结构。

可能有多种的方式实现这个功能,方案1,客户端通常是读取syslog,然后封装为GPB,发送给服务器,服务端收取GPB然后按照自己的格式加入到大数据系统。

方案2,客户端写gpb文件,通过ftp等传递给服务器,然后服务器解析,或者直接保存。

总的来说均需要写入和读取。因此我们仅介绍写入和读取功能。因此分三部分介绍,分别为读取,写入,两者之间的协议定义。

3. GPB对象定义(协议定义)

我们用于交换数据的的访问日志,包含以下及部分:

  • srcIP IP地址,占用四个字节。
  • srcPort 源端口,占用两个字节。
  • Agent; 字符串, 设定最大占用64字节。
  • 目标url: 字符串, 设定最大占用128字节(如果超长即截断)
  • 访问时间, 自1970年的秒值。占用4个字节。
package bj;
message WebLog{
{
  required int32 time = 1;
  required int32 srcIP = 2;
  required int32 srcPort = 3;
  required string url = 4;
  optional string Agent = 5;
}

两种类型,一种为整形数字,另外一种为字符串。还有两种修饰符,一种为required,表示必须带有,如果没有将判断为错误。另外一种为optional,表示可选的,如果没有不做处理。GPB在保存时仅保存了数字索引,因此其占用空间非常小,但因此其保存的文件失去了一定的自解释性。

保存为文件weblog.proto,然后编译生成C++代码,命令为:

protoc --cpp_out=./ weblog.proto

这将生成weblog.pb.cc weblog.pb.h两个文件,这样两者之间的协议就定义好了,客户端使用这个头文件中定义的方法来写入数据,服务器通过方法来读取数据。

使用protoc命令来编译gpb协议定义文件时,对于C++来说,编译器会为每一个消息创建一个对应的类,如果定义了packet,那将定义一个名字空间。 –cpp_out表示在指定的目录里面生成c++代码,可以使用“protoc -h”来查看更多的用法。下一步我们来编写读取代码。

4. gpb数据生成

写入文件:

int write()
{
   bj::WebLog log;
   int now = time(NULL);
   log.set_timestamp(now);
   log.set_srcip(1);
   log.set_srcport(2);
   log.set_url("http://www.sina.com.cn/news");
   log.set_agent("firefox");

   fstream output("a.log", ios::out | ios::app | ios::binary);

   if(!log.SerializeToOstream(&output)){
      cout << "Failed to write msg."<<endl;
      return -1;
   }
   return 0;
}

追加到最后。

5. gpb数据读取

int read()
{
  bj::WebLog log;
  fstream fin("a.log", ios::in | ios::binary);
  if(!log.ParseFromIstream(&fin))
  {
      cout<<"Failed to parrse word! "<< endl;
      return -1;
  }
  cout<<log.url()<<endl;
  cout<<log.agent()<<endl;
  return 0;

}

读取第一个并输出。

6. Python使用

python setup.py build
python setup.py  test
python setup.py  install