赖勇浩(http://laiyonghao.com)
快刀斩乱麻,祭上最后两个 rpc 分析,再整上我自己的设计,这个系列就完结了。
好,废话不多说,看看这个 protobuf-socket-rpc,简介是 Java and Python protobuf rpc implementation using tcp/ip sockets,听说支持 java 的服务器和 python 的客户端,有点怪怪地哈?基本格式见:http://code.google.com/p/protobuf-socket-rpc/source/browse/trunk/proto/rpc.proto ,引用全文如下:
package protobuf.socketrpc;option java_package = "com.googlecode.protobuf.socketrpc";option java_outer_classname = "SocketRpcProtos";message Request { // RPC service full name required string service_name = 1; // RPC method name required string method_name = 2; // RPC request proto required bytes request_proto = 3;}message Response { // RPC response proto optional bytes response_proto = 1; // Error, if any optional string error = 2; // Was callback invoked optional bool callback = 3 [default = false]; // Error Reason optional ErrorReason error_reason = 4;}// Possible error reasons// The server-side errors are returned in the response from the server.// The client-side errors are returned by the client-side code when it doesn't// have a response from the server.enum ErrorReason { // Server-side errors BAD_REQUEST_DATA = 0; // Server received bad request data BAD_REQUEST_PROTO = 1; // Server received bad request proto SERVICE_NOT_FOUND = 2; // Service not found on server METHOD_NOT_FOUND = 3; // Method not found on server RPC_ERROR = 4; // Rpc threw exception on server RPC_FAILED = 5; // Rpc failed on server // Client-side errors (these are returned by the client-side code) INVALID_REQUEST_PROTO = 6; // Rpc was called with invalid request proto BAD_RESPONSE_PROTO = 7; // Server returned a bad response proto UNKNOWN_HOST = 8; // Could not find supplied host IO_ERROR = 9; // I/O error while communicating with server}
很眼熟啊,是的,跟之前看过的 fepss-rpc 几乎一样,除了这里定义了更多的错误类型,连注释都几乎一模一样啊,不知道谁抄谁的。所以没啥好说的了,唯一可以唠叨一下的是它跟 fepss-rpc 都采用无 ID 的设计,所以不能 parallel pipelining,细节见(http://blog.csdn.net/lanphaday/archive/2011/04/11/6316099.aspx)。
以 Python 始,再以 Python 终,最后一个是 protocol buffers (http://code.google.com/p/protobuf/) RPC implemented using python twisted framework.,基于 twisted 的 rpc,虽然我不喜欢 twisted,但不影响我看 txprotobuf 哈。它的基本格式见:http://bazaar.launchpad.net/~proppy/txprotobuf/master/view/head:/protobuf/txprotobuf.proto ,照例全文引过来:
// Copyright (c) 2008 Johan Euphrosine// See LICENSE for details.message Call { required uint32 token = 1; required string method = 2; required bytes request = 3;}message Result { required uint32 token = 1; required bytes response = 2;}message Box { optional bytes call = 1; optional bytes result = 2;}
简洁,跟 casocklib 相似的设计。唯一能够说一下的就是只有 method 字段而没有 service 字段所以没有办法做到 multi-service。另外,Box 这个命名我很喜欢。
本来想单独作为一篇的,后来发现前两个实在没啥好说啊,干脆就整合在一起得了。好,上代码:
enum PackageType { HEARTBEAT = 0; ERROR = 1; REQUEST = 2; RESPONSE = 3;};message Heartbeat {};message Error { required string info = 1;};message Request { required string service = 1; required string method = 2; optional bytes request = 3;};message Response { optional bytes response = 1;};message Package { required PackageType type = 1; required uint32 identify = 2; required bytes serialized = 3;};message Placeholder {};
首先是学习 server1 定义了几种数据包的类型,不过我扩展了一下,加了心跳和错误这两种包。心跳在这里就是简单的一个空包,只是激活一下 TCP 连接,以免超时断开。如果要复杂一点的,可以参考陈硕《分布式系统的工程化开发方法》 p.51 心跳协议的设计(http://blog.csdn.net/Solstice/archive/2010/10/19/5950190.aspx)。
message Error 很简单,只有一个 info 字段。之前也在想要不要加个数字枚举来让调用端了解一下基本出了啥错,后来我想还是不要了。主要是出错了对于调用端来说没有什么挽救手段啊,只能是往日志里写一句,然后该干啥干啥去,既然如此,又何必多此一举呢,直接把错误的文本从服务器端传过来就好了。
message Request 带了 service 和 method 字段,为的是明晰地表达咱支持 multi-service。而的 request 字段是 optional 的,为的是支持“空”参数调用 RPC。Message Response 的 optional bytes response 也是类似,为了支持“空”返回值。因为 protobuf 的 rpc 声明不支持 rpc XXX()returns() 这样的无参数无返回值的声明,所以我又在后面声明了一个 message Placeholder,这样 rpc 声明就可以写成 rpc XXX(Placeholder)returns(Placeholder)。调用代码时,可以直接写成 proxy.service.XXX() 这样子,由我们自己的 Proxy/Channel 实现去处理好这个占位参数和返回值就行了。
最后,整个连接上传输的数据包就只有一种:message Package,不论是 Heartbeat、Error、Rquest 或 Response,最后都会序列化到 Package.serialized 里去。
对比这么多实现,我的这个版本既没有像 protobuf-remote 般自由开放的调用参数机制,也没有 protobuf-rpc-pro 可以返回超大块数据的内置方案。不过我还是比较满意,太自由和太重量的方案不符合我的技术观。
我打算基于 gevent 实现这个 rpc。
===== 完 ======