在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
------------------------------------------------------------------------
RPC 功能目标RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用,在前文《浅出篇》中给出了一种实现结构,基于 stub 的结构来实现。下面我们将具体细化 stub 结构的实现。 RPC 调用分类RPC 调用分以下两种:
异步和同步的区分在于是否等待服务端执行完成并返回结果。 RPC 结构拆解《浅出篇》给出了一个比较粗粒度的 RPC 实现概念结构,这里我们进一步细化它应该由哪些组件构成,如下图所示。 RPC 服务方通过 RPC 服务端接收器 RPC 组件职责上面我们进一步拆解了 RPC 实现结构的各个组件组成部分,下面我们详细说明下每个组件的职责划分。
RPC 实现分析在进一步拆解了组件并划分了职责之后,这里以在 java 平台实现该 RPC 框架概念模型为例,详细分析下实现中需要考虑的因素。 导出远程接口导出远程接口的意思是指只有导出的接口可以供远程调用,而未导出的接口则不能。在 java 中导出接口的代码片段可能如下:
我们可以导出整个接口,也可以更细粒度一点只导出接口中的某些方法,如:
java 中还有一种比较特殊的调用就是多态,也就是一个接口可能有多个实现,那么远程调用时到底调用哪个?这个本地调用的语义是通过 jvm 提供的引用多态性隐式实现的,那么对于 RPC 来说跨进程的调用就没法隐式实现了。如果前面
上面 demo2 是另一个实现,我们标记为 "demo2" 来导出,那么远程调用时也需要传递该标记才能调用到正确的实现类,这样就解决了多态调用的语义。 导入远程接口与客户端代理导入相对于导出远程接口,客户端代码为了能够发起调用必须要获得远程接口的方法或过程定义。目前,大部分跨语言平台 RPC 框架采用根据 IDL 定义通过 code generator 去生成 stub 代码,这种方式下实际导入的过程就是通过代码生成器在编译期完成的。我所使用过的一些跨语言平台 RPC 框架如 CORBAR、WebService、ICE、Thrift 均是此类方式。 代码生成的方式对跨语言平台 RPC 框架而言是必然的选择,而对于同一语言平台的 RPC 则可以通过共享接口定义来实现。在 java 中导入接口的代码片段可能如下:
在 java 中 'import' 是关键字,所以代码片段中我们用 refer 来表达导入接口的意思。这里的导入方式本质也是一种代码生成技术,只不过是在运行时生成,比静态编译期的代码生成看起来更简洁些。java 里至少提供了两种技术来提供动态代码生成,一种是 jdk 动态代理,另外一种是字节码生成。动态代理相比字节码生成使用起来更方便,但动态代理方式在性能上是要逊色于直接的字节码生成的,而字节码生成在代码可读性上要差很多。两者权衡起来,个人认为牺牲一些性能来获得代码可读性和可维护性显得更重要。 协议编解码客户端代理在发起调用前需要对调用信息进行编码,这就要考虑需要编码些什么信息并以什么格式传输到服务端才能让服务端完成调用。出于效率考虑,编码的信息越少越好(传输数据少),编码的规则越简单越好(执行效率高)。我们先看下需要编码些什么信息:
除了以上这些必须的调用信息,我们可能还需要一些元信息以方便程序编解码以及未来可能的扩展。这样我们的编码消息里面就分成了两部分,一部分是元信息、另一部分是调用的必要信息。如果设计一种 RPC 协议消息的话,元信息我们把它放在协议消息头中,而必要信息放在协议消息体中。下面给出一种概念上的 RPC 协议消息设计格式:
格式确定后编解码就简单了,由于头长度一定所以我们比较关心的就是消息体的序列化方式。序列化我们关心三个方面: 传输服务协议编码之后,自然就是需要将编码后的 RPC 请求消息传输到服务方,服务方执行后返回结果消息或确认消息给客户方。RPC 的应用场景实质是一种可靠的请求应答消息流,和 HTTP 类似。因此选择长连接方式的 TCP 协议会更高效,与 HTTP 不同的是在协议层面我们定义了每个消息的唯一 id,因此可以更容易的复用连接。 既然使用长连接,那么第一个问题是到底 client 和 server 之间需要多少根连接?实际上单连接和多连接在使用上没有区别,对于数据传输量较小的应用类型,单连接基本足够。单连接和多连接最大的区别在于,每根连接都有自己私有的发送和接收缓冲区,因此大数据量传输时分散在不同的连接缓冲区会得到更好的吞吐效率。所以,如果你的数据传输量不足以让单连接的缓冲区一直处于饱和状态的话,那么使用多连接并不会产生任何明显的提升,反而会增加连接管理的开销。 连接是由 client 端发起建立并维持。如果 client 和 server 之间是直连的,那么连接一般不会中断(当然物理链路故障除外)。如果 client 和 server 连接经过一些负载中转设备,有可能连接一段时间不活跃时会被这些中间设备中断。为了保持连接有必要定时为每个连接发送心跳数据以维持连接不中断。心跳消息是 RPC 框架库使用的内部消息,在前文协议头结构中也有一个专门的心跳位,就是用来标记心跳消息的,它对业务应用透明。 执行调用client stub 所做的事情仅仅是编码消息并传输给服务方,而真正调用过程发生在服务方。server stub 从前文的结构拆解中我们细分了 java 中实现代码的动态接口调用目前一般通过反射调用。除了原生的 jdk 自带的反射,一些第三方库也提供了性能更优的反射调用,因此 调用过程的控制需要考虑哪些因素,
RPC 异常处理无论 RPC 怎样努力把远程调用伪装的像本地调用,但它们依然有很大的不同点,而且有一些异常情况是在本地调用时绝对不会碰到的。在说异常处理之前,我们先比较下本地调用和 RPC 调用的一些差异: 由于 RPC 固有的消耗相对本地调用高出几个数量级,本地调用的固有消耗是纳秒级,而 RPC 的固有消耗是在毫秒级。那么对于过于轻量的计算任务就并不合适导出远程接口由独立的进程提供服务,只有花在计算任务上时间远远高于 RPC 的固有消耗才值得导出为远程接口提供服务。 总结至此我们提出了一个 RPC 实现的概念框架,并详细分析了需要考虑的一些实现细节。无论 RPC 的概念是如何优雅,但是“草丛中依然有几条蛇隐藏着”,只有深刻理解了 RPC 的本质,才能更好地应用。
前言: 执行过程 2. boost安装/编译/链接 1 #include <iostream> 2 #include <string> 3 #include <boost/regex.hpp> 4 int main() 5 { 6 boost::regex pattern("\\w+@\\w+(\\.\\w+)*"); 7 std::string mail("[email protected]"); 8 9 if ( boost::regex_match(mail, pattern) ) { 10 std::cout << mail << " is a valid mail address!" << std::endl; 11 } else { 12 std::cout << mail << " is not a valid mail address!" << std::endl; 13 } 14 } 安装boost和配置visual studio的参考网址如下所示: 3. libevent的编译/安装/链接 1 #ifdef WIN32 2 #include <winsock2.h> 3 #include <ws2tcpip.h> 4 #pragma comment(lib,"ws2_32.lib") 5 #define WIN32_LEAN_AND_MEAN 6 #include <windows.h> 7 #undef WIN32_LEAN_AND_MEAN 8 #include <io.h> 9 #include <tchar.h> 10 #endif nmake /f Makefile.nmake
具体添加的原则是编译缺那个头文件, 就去添加相关的系统头文件目录
编写libevent代码编译/运行成功
1 #include <event.h> 2 #include <stdio.h> 3 4 int main() 5 { 6 const char *version = event_get_version(); 7 printf("%s\n",version); 8 return 0; 9 } 附加依赖项: ws2_32.lib , libevent_core.lib ,libevent.lib, libevent_extras.lib
4. thrift的编译/链接 1 assert.h 2 3 #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) ) 4 5 #define assert(_Expression) (void)( (!!(_Expression)) || (::_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) ) 6 测试验证:
编写 hello.thrift 文件
1 namespace cpp test 2 3 service HelloService { 4 string hello(1: string username); 5 } thrift.exe -gen cpp hello.thrift
1 2014/05/07 11:35 9,215 HelloService.cpp 2 2014/05/07 11:26 7,117 HelloService.h 3 2014/05/07 11:26 1,422 HelloService_server.skeleton.cpp 4 2014/05/07 11:35 268 hello_constants.cpp 5 2014/05/07 11:35 357 hello_constants.h 6 2014/05/07 11:35 202 hello_types.cpp 7 2014/05/07 11:35 367 hello_types.h 编译执行, 遇到10093错误, 如何去解决?
WSANOTINITIALISED, which means WSAStartup() has not been called yet.
编译完成后运行时会报WSAStartup错误
解决方案:
http://hi.baidu.com/fsx92/item/9f7a96efd33f9f1b585dd88c
编写测试case
服务端代码:
1 class HelloServiceHandler : virtual public HelloServiceIf { 2 public: 3 HelloServiceHandler() { 4 // Your initialization goes here 5 } 6 7 void hello(std::string& _return, const std::string& username) { 8 _return = "hello " + username; 9 } 10 11 }; 12 13 int main(int argc, char **argv) { 14 int port = 9090; 15 16 TWinsockSingleton::create(); // 需要用户自己添加, 进行WSAStartup的初始化, 算是windows 版的thrift的一个疏忽 17 18 shared_ptr<HelloServiceHandler> handler(new HelloServiceHandler()); 19 shared_ptr<TProcessor> processor(new HelloServiceProcessor(handler)); 20 shared_ptr<TServerTransport> serverTransport(new TServerSocket(port)); 21 shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory()); 22 shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory()); 23 24 TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); 25 server.serve(); 26 return 0; 27 } 客户端代码
1 2 #include "HelloService.h" 3 #include <thrift/protocol/TBinaryProtocol.h> 4 #include <thrift/server/TSimpleServer.h> 5 #include <thrift/transport/TBufferTransports.h> 6 #include <thrift/transport/TSocket.h> 7 8 using namespace ::apache::thrift; 9 using namespace ::apache::thrift::protocol; 10 using namespace ::apache::thrift::transport; 11 using namespace ::apache::thrift::server; 12 13 using boost::shared_ptr; 14 15 using namespace ::test; 16 17 int main() 18 { 19 20 shared_ptr<TTransport> socket(new TSocket("127.0.0.1", 9090)); 21 shared_ptr<TTransport> transport(new TBufferedTransport(socket)); 22 shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); 23 24 HelloServiceClient client(protocol); 25 26 try { 27 transport->open(); 28 std::string res; 29 client.hello(res, "lilei"); 30 std::cout << res << std::endl; 31 } catch(TException &e) { 32 std::cout << e.what() << std::endl; 33 } 34 35 return 0; 36 } 37 推荐做法:
对依赖库的整理, 这是一个好的习惯
每个库单独创建一个头文件目录, 和库文件目录, 所有的库统一在同一个库仓库下, c++的库管理不如java的maven那么方便, 又进入一个石器时代, 库的维护需要开发者手动去支持, 但这是种很好的工程实践.
repository目录为顶级的仓库目录, 以boost为例, boost表示库名, 之下boost/1.55.0为boost的具体的某一版本, 而boost/0.55.0/include为这个版本的头文件目录, boost/0.55.0/lib为这个版本的lib库目录
参考 http://blog.csdn.net/colouroo/article/details/38588297
前言: 执行过程 2. boost安装/编译/链接 1 #include <iostream> 2 #include <string> 3 #include <boost/regex.hpp> 4 int main() 5 { 6 boost::regex pattern("\\w+@\\w+(\\.\\w+)*"); 7 std::string mail("[email protected]"); 8 9 if ( boost::regex_match(mail, pattern) ) { 10 std::cout << mail << " is a valid mail address!" << std::endl; 11 } else { 12 std::cout << mail << " is not a valid mail address!" << std::endl; 13 } 14 } 安装boost和配置visual studio的参考网址如下所示: 3. libevent的编译/安装/链接 1 #ifdef WIN32 2 #include <winsock2.h> 3 #include <ws2tcpip.h> 4 #pragma comment(lib,"ws2_32.lib") 5 #define WIN32_LEAN_AND_MEAN 6 #include <windows.h> 7 #undef WIN32_LEAN_AND_MEAN 8 #include <io.h> 9 #include <tchar.h> 10 #endif nmake /f Makefile.nmake
具体添加的原则是编译缺那个头文件, 就去添加相关的系统头文件目录
编写libevent代码编译/运行成功
1 #include <event.h> 2 #include <stdio.h> 3 4 int main() 5 { 6 const char *version = event_get_version(); 7 printf("%s\n",version); 8 return 0; 9 } 附加依赖项: ws2_32.lib , libevent_core.lib ,libevent.lib, libevent_extras.lib
4. thrift的编译/链接 1 assert.h 2 3 #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) ) 4 5 #define assert(_Expression) (void)( (!!(_Expression)) || (::_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) ) 6 测试验证:
编写 hello.thrift 文件
1 namespace cpp test 2 3 service HelloService { 4 string hello(1: string username); 5 } thrift.exe -gen cpp hello.thrift
1 2014/05/07 11:35 9,215 HelloService.cpp 2 2014/05/07 11:26 7,117 HelloService.h 3 2014/05/07 11:26 1,422 HelloService_server.skeleton.cpp 4 2014/05/07 11:35 268 hello_constants.cpp 5 2014/05/07 11:35 357 hello_constants.h 6 2014/05/07 11:35 202 hello_types.cpp 7 2014/05/07 11:35 367 hello_types.h 编译执行, 遇到10093错误, 如何去解决?
WSANOTINITIALISED, which means WSAStartup() has not been called yet.
编译完成后运行时会报WSAStartup错误
解决方案:
http://hi.baidu.com/fsx92/item/9f7a96efd33f9f1b585dd88c
编写测试case
服务端代码:
1 class HelloServiceHandler : virtual public HelloServiceIf { 2 public: 3 HelloServiceHandler() { 4 // Your initialization goes here 5 } 6 7 void hello(std::string& _return, const std::string& username) { 8 _return = "hello " + username; 9 } 10 11 }; 12 13 int main(int argc, char **argv) { 14 int port = 9090; 15 16 TWinsockSingleton::create(); // 需要用户自己添加, 进行WSAStartup的初始化, 算是windows 版的thrift的一个疏忽 17 18 shared_ptr<HelloServiceHandler> handler(new HelloServiceHandler()); 19 shared_ptr<TProcessor> processor(new HelloServiceProcessor(handler)); 20 shared_ptr<TServerTransport> serverTransport(new TServerSocket(port)); 21 shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory()); 22 shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory()); 23 24 TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); 25 server.serve(); 26 return 0; 27 } 客户端代码
1 2 #include "HelloService.h" 3 #include <thrift/protocol/TBinaryProtocol.h> 4 #include <thrift/server/TSimpleServer.h> 5 #include <thrift/transport/TBufferTransports.h> 6 #include <thrift/transport/TSocket.h> 7 8 using namespace ::apache::thrift; 9 using namespace ::apache::thrift::protocol; 10 using namespace ::apache::thrift::transport; 11 using namespace ::apache::thrift::server; 12 13 using boost::shared_ptr; 14 15 using namespace ::test; 16 17 int main() 18 { 19 20 shared_ptr<TTransport> socket(new TSocket("127.0.0.1", 9090)); 21 shared_ptr<TTransport> transport(new TBufferedTransport(socket)); 22 shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); 23 24 HelloServiceClient client(protocol); 25 26 try { 27 transport->open(); 28 std::string res; 29 client.hello(res, "lilei"); 30 std::cout << res << std::endl; 31 } catch(TException &e) { 32 std::cout << e.what() << std::endl; 33 } 34 35 return 0; 36 } 37 推荐做法:
对依赖库的整理, 这是一个好的习惯
每个库单独创建一个头文件目录, 和库文件目录, 所有的库统一在同一个库仓库下, c++的库管理不如java的maven那么方便, 又进入一个石器时代, 库的维护需要开发者手动去支持, 但这是种很好的工程实践.
repository目录为顶级的仓库目录, 以boost为例, boost表示库名, 之下boost/1.55.0为boost的具体的某一版本, 而boost/0.55.0/include为这个版本的头文件目录, boost/0.55.0/lib为这个版本的lib库目录
参考 http://blog.csdn.net/colouroo/article/details/38588297
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论