protobuf 的运用

关于Protobuf

protobuf是google的一个开源序列化框架,基于二进制数据交换格式,兼顾了效率和灵活性。详见http://code.google.com/p/protobuf/

本文假定读者对protobuf已经有了初步接触,故略过一些基本和细节的描述,着重于介绍protobuf在笔者项目中的应用思路。因项目主要编程语言 是c++,所以本文的示例着眼于如何在c++中借助protobuf简化一些通用模块的处理。对于其它语言(java,python等),如果语言本身没 有更好用的特性或者更方便的库的话,也可采取类似做法。

不仅仅是网络消息序列化

虽然protobuf最广泛的应用是网络数据交换,但在使用过程中发现其框架本身的设计具有极灵活的扩展性,对于服务器来说,完全可以作为通用的数据解析层。一言以蔽之,即可以将各种形式的外部数据以非常易用的方式存取,通过最自然的本地语言对象进行操作(在本文中是c++)。借助protobuf框架的以下特性,构建protobuf生态圈将会是一个轻松愉快的过程:

1.代码生成器

protobuf提供了自定义代码生成器的支持——“代码生成器”远没有它听起来那么复杂,相反,得益于protobuf合理的架构设计,笔者项目中的各类生成器一般只有几百行代码。

protobuf的代码生成器可以跟静态语言的编译器进行类比,编译器:

源码 ---> (编译器前端) ---> 中间代码 ---> (编译器后端) ---> 机器码

而protobuf的代码生成过程如下:

proto文件 ---> (解析器) --->  本地语言对象模型 ---> (代码生成器) ---> 本地语言源码

我们要做的仅仅是自定义代码生成后端,只需关心proto文件中定义的对象模型即可,复杂的proto文件解析由protoc内置的解析器完成。

2.反射机制

protobuf提供了反射机制(包括c++的版本),以实现对运行期对象进行内省。相关示例代码见http://code.google.com/apis/protocolbuffers/docs/reference/cpp/google.protobuf.message.html

借助反射,可以完成一些在传统c++设计领域中难以完成的事情,比如对一个对象的所有属性进行迭代,比如根据属性的名字进行存取等等。其实现也相当高效,例如如下代码:

Message* message = ...;  
const Reflection* reflection = ...;  
const FieldDescriptor* field = ...;  
reflection->SetInt32(message, field);

最后一行对于message相应属性的定位是直接通过地址偏移来实现的(有点hack的意味),其偏移值通过代码生成器生成并保存在FieldDescriptor中。

案例一:利用Protobuf实现数据库粘合层

动机:

由于语言特性的原因,c++中对数据库的操作一直是个比较繁琐的过程。以mysql为例,官方的c api远不适合不加封装直接使用,mysql++库的Specialized SQL Structures尚可,但也还不够便利,比如没有has语义使得只update一个结构的其中几个字段的写法比较冗长,比如表的字段有增删需要手动维护c++中相应的结构等等。

利用protobuf,完全可以实现更易用而强大的数据库粘合层。

简单示例:

以下是笔者项目中数据库粘合层的用法示例(为了便于说明,做了些简化,没有演示索引、字符集、存储引擎等功能)。

首先,定义proto文件。

test_data.proto:

message TestData  
{  
    required int32 id = 1;  
    optional string name = 2;  
}

这样,自定义的代码生成器会生成以下sql(在笔者的项目中,这一步是自动完成的,每当有需要导出sql的proto文件,则编译时自动生成相应sql并执行,相应的DROP语句是版本降级时用的):

CREATE TABLE TESTDATA(  
    id INT NOT NULL,  
    name VARCHAR);  
DROP TABLE TESTDATA;

在c++中,就可以用以下方式操作:

Query query = database->Query();  
TestData data;    // proto文件中定义的对象  
data.set_id(1);  
data.set_name("Snake");  
if (!query.Insert(data)) { // INSERT INTO TESTDATA (id, name) VALUES(1, "Snake");  
    ....  
}  
data.set_name("Raiden");  
TestData where;  
where.set_id(1);  
if (!query.Update(data, where)) { // 通过同类型对象来描述where子句, 相当于where id=1  
    ...  
}  
std::vector<TestData> messages;  
query.Select(messages,     // 把结果集储存到messages中,接受std顺序容器, 也接受单个message  
             Columns("id")("name"),   // 指定column, SELECT * 可用AllColumns()  
             Where("id") == 1 && Where("name") == "Raiden",    // 另一种形式的WHERE子句  
             OrderBy("id").Desc(),   // ORDER BY ID DESC  
             Limit(10));   // LIMIT 10, 仅作为示例  
for (size_i = 0; i < messages.size(); ++i) {  
    std::cout << messages[i].DebugString();  
}  
query.Delete<TestData>();    // DELETE FROM TESTDATA, 同样支持两种形式的WHERE

以上只是基本用法示例。在笔者项目中实现的一些扩展功能还有(主要是利用protobuf的option机制实现):

1、导出控制。可以灵活设置单个proto文件、单个消息、单个字段是否需要导出。

2、版本号控制。可以指明某个字段的版本号,自动化生成相应脚本,方便运营时升级/降级。

4、对复杂结构的支持。原生数据类型基本上都可以跟sql中的数据类型一一对应,对于repeated字段和子message,采用blob类型存储其序列化后的数据。

5、数据库相应选项声明。比如设置表索引、所用引擎、字符集、字段长度、auto_increment等等。

6、增量update。可以自动比较message与上次update的差异,只update有改动的字段。

考虑如下案例:给原有的数据库表增加一个字段,读写,发送给其它网络主机进行处理。采用最原始的纯手工方式需要经历以下步骤:

1、手动写alter table并执行;

2、给c++中相应结构增加字段;

3、修改insert,select,update等等涉及该字段的sql;

4、修改相关网络同步的消息代码,增加此字段的同步;

很快,涉及数据库的改动成为了bug滋生的温床。

再看看前述方案,比如需要给TestData增加一新字段new_column,只需要做这么一件事,很好的体现了SPOT原则:

message TestData  
{  
    required int32 id = 1;  
    optional string name = 2;  
    optional float new_column = 3 [(sql.column_version) = "1.0.1"];  
}

new_column 为新增字段,其后跟随的选项描述了其版本号。不用再做其它操作——alter table的sql语句被自动生成并执行,update、insert、select等调用自动兼容(内部实现是通过反射机制来拼接sql语句的),结构 天生为网络数据交换而设计,所以不用改动网络同步相关代码,也不用操心版本不匹配的问题。Perfect!

...待续,下篇将介绍protobuf与lua结合、用protobuf进行配置文件解析等方案。

时间: 2024-10-11 10:13:14

protobuf 的运用的相关文章

python通过protobuf实现rpc

由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行google,这里只是做个简单的介绍.rpc的主要功能是让分布式系统的实现更为简单,为提供强大的远程调用而不损失本地调用语义的简洁性.为了实现这个目标,rpc框架需要提供一种透明调用机制让使用者不必显示区分本地调用还是远程调用.rpc架构涉及的组件如下: 客户方像调用本地方法一样去调用远程接口方法,R

Centos6.4下安装protobuf及简单使用

1.protobuf是google公司提出的数据存储格式,详细介绍可以参考:https://code.google.com/p/protobuf/ 2.下载最新的protobuf,下载地址:https://code.google.com/p/protobuf/downloads/list 3.下载protobuf2.5.o版本,protobuf-2.5.0.tar.gz解压并进行安装. 解压:tar xvf protobuf-2.5.0.tar.gz 安装步骤:(1)./configure (2

基于protobuf的RPC实现

可以对照使用google protobuf RPC实现echo service一文看,细节本文不再描述. google protobuf只负责消息的打包和解包,并不包含RPC的实现,但其包含了RPC的定义.假设有下面的RPC定义: service MyService { rpc Echo(EchoReqMsg) returns(EchoRespMsg) } 那么要实现这个RPC需要最少做哪些事?总结起来需要完成以下几步: 客户端 RPC客户端需要实现google::protobuf::RpcCh

Windows下编译protobuf v3.3.0

一:概述 关于 protobuf 在此不再多说,此处记录下成功编译步骤以备日后查阅.注意:本文并不是使用cmake gui进行编译的,如果熟悉cmake gui的话,也可以使用gui进行生成编译. 二:准备资源及工具 VS2013或以上版本,安装好 protobuf 源码,下载地址:官网:http://code.google.com/p/protobuf/git网:https://github.com/google/protobuf或git:https://github.com/google/p

Skynet服务器框架(五) 使用pbc(protobuf)

引言: 假如我们要建立的skynet服务器与客户端的连接方式为长连接,且选择了Google的Protobuf来定制我们的网络协议,那么,接下来我们要解决的问题就是:如何在skynet框架中使用socket+protobuf. API 几个常用的skynet接口: * 输出错误信息: skynet.error(...) * 获取本地服务句柄方式: skynet.localname(...) * 设置定时器方式: skynet.timeout(...) * skynet强制退出方式: skyname

uLua/toLua加载protobuf转lua的table为bool的解决方法

当我们加载protobuf对应的lua的table的时候,我们使用如下方式来加载 local person_pb = require 'Protol.person_pb' 注意,这个table前面的Protol.这段一定不能去掉,如果去掉了,你加载到的persob_pb将会是一个bool类型的值. 如果加上这个Protol.的话,你才能加载到真正有的数据表. 也许是lua和protobuf工具关联时的一个约定,也许是这样,反正要放在protol文件夹下就能加载到数据表.

序列化之protobuf与avro对比(Java)

最近在做socket通信中用到了关于序列化工具选型的问题,在调研过程中开始趋向于用protobuf,可以省去了编解码的过程.能够实现快速开发,且只需要维护一份协议文件即可. 但是调研过程中发现了protobuf的一些弊端,比如需要生成相应的文件类,和业务绑定太紧密,所以在看了AVRO之后发现它完美解决了这个问题. 下面记录下对这两种序列化工具的入门与测评. 一.protobuf基本操作 protobuf简介: Protocol Buffers (a.k.a., protobuf) are Goo

Golang版protobuf编译

官方网址: https://developers.google.com/protocol-buffers/ (需要FQ) 代码仓库: https://github.com/google/protobuf  (C++) https://github.com/golang/protobuf  (Golang) https://developers.google.com/protocol-buffers/docs/gotutorial (英文版教程) 本文以下部分按照英文版教程操作(windows  

[z]protobuf实现c++与java之间的数据传递,beancopy数据到前台

[z]http://blog.csdn.net/xhyzdai/article/details/46684335 定义proto文件 [plain] view plain copy option java_package = "com.wy.web"; message my_message{ required string startedTime =1; required string version=2; required double configuredCapacity=3; r

protobuf 学习 收藏的文章

Protobuf数据格式解析: packed repeated与repeated的区别在于编码方式不一样,repeated将多个属性类型与值分开存储.而packed repeated采用Length-delimited方式. proto3的repeated默认就是使用packed这种方式来存储,(proto2与proto3区别在于.proto的语法). http://blog.csdn.net/zhaozheng7758/article/details/6749000: [packed =fal