百度开源高性能RPC框架 sofa-pbrpc

OYIKurt 3年前
   <h2><strong>简介</strong></h2>    <p><a href="/misc/goto?guid=4959716956567576429" rel="nofollow,noindex"><strong>sofa-pbrpc</strong> </a> 是基于Google Protocol Buffers 实现的RPC网络通信库,在百度公司各部门得到广泛使用,每天支撑上亿次内部调用。sofa-pbrpc基于百度大搜索高并发高负载的业务场景不断打磨,成为一套简单易用的轻量级高性能RPC框架。2014年sofa-pbrpc正式对外开源受到广大开发人员的关注,目前sofa-pbrpc已经在浪潮、金山、乐视等各大互联网公司产品中使用。</p>    <h2><strong>目标</strong></h2>    <ul>     <li> <p>轻量</p> </li>     <li> <p>易用</p> </li>     <li> <p>高性能</p> </li>    </ul>    <h2><strong>特性</strong></h2>    <ul>     <li> <p>接口简单,容易使用</p> </li>     <li> <p>实现高效,性能优异(高吞吐、低延迟、高并发连接数)</p> </li>     <li> <p>测试完善,运行稳定</p> </li>     <li> <p>支持同步和异步调用,满足不同类型需求</p> </li>     <li> <p>支持多级超时设定,灵活控制请求超时时间</p> </li>     <li> <p>支持精准的网络流量控制,对应用层透明</p> </li>     <li> <p>支持透明压缩传输,节省带宽</p> </li>     <li> <p>提供服务和方法级别的服务调用统计信息,方便监控</p> </li>     <li> <p>支持自动建立连接和自动重连,用户无需感知连接</p> </li>     <li> <p>远程地址相同的Client Stub共享一个连接通道,节省资源</p> </li>     <li> <p>空闲连接自动关闭,及时释放资源</p> </li>     <li> <p>支持Mock测试</p> </li>     <li> <p>支持多Server负载均衡与容错</p> </li>     <li> <p>原生支持HTTP协议访问</p> </li>     <li> <p>提供内建的Web监控页面</p> </li>     <li> <p>提供Python客户端库</p> </li>     <li> <p>支持webservice,用户快速定义web server处理逻辑</p> </li>     <li> <p>支持profiling,实时查看程序的资源消耗,方便问题追查</p> </li>    </ul>    <p><img src="https://simg.open-open.com/show/8f6fe3dbc3ad1f425634cb025d3aff24.png"></p>    <h2><strong>快速使用</strong></h2>    <p>使用sofa-pbrpc只需要三步:</p>    <ul>     <li> <p>定义通讯协议</p> </li>     <li> <p>实现Server</p> </li>     <li> <p>实现Client</p> </li>    </ul>    <p>样例代码参见“sample/echo”。</p>    <h2><strong>定义通讯协议</strong></h2>    <p>定义协议只需要编写一个proto文件即可。 范例:echo_service.proto</p>    <pre>  <code class="language-cpp">c++  package sofa.pbrpc.test;  option cc_generic_services = true;  message EchoRequest {  required string message = 1;  }  message EchoResponse {  required string message = 1;  }  service EchoServer {  rpc Echo(EchoRequest) returns(EchoResponse);  }</code></pre>    <p>使用protoc编译'echo_service.proto',生成接口文件'echo_service.pb.h'和'echo_service.pb.cc'。</p>    <p>注意:</p>    <ul>     <li> <p>package会被映射到C++中的namespace,为了避免冲突建议使用package;</p> </li>     <li> <p>需要设置“cc_generic_services”,以通知protoc工具生成RPC框架代码;</p> </li>     <li> <p>这里EchoRequest和EchoResponse的成员完全相同,在实际应用中可以设置不同的成员;</p> </li>    </ul>    <h2><strong>实现Server</strong></h2>    <p>头文件</p>    <pre>  <code class="language-cpp">#include <sofa/pbrpc/pbrpc.h>  // sofa-pbrpc头文件  #include "echo_service.pb.h"   // service接口定义头文件</code></pre>    <p>实现服务</p>    <pre>  <code class="language-cpp">Impl() {}    private:      virtual void Echo(google::protobuf::RpcController* controller,                        const sofa::pbrpc::test::EchoRequest* request,                        sofa::pbrpc::test::EchoResponse* response,                        google::protobuf::Closure* done)      {          sofa::pbrpc::RpcController* cntl =              static_cast<sofa::pbrpc::RpcController*>(controller);          SLOG(NOTICE, "Echo(): request message from %s: %s",              cntl->RemoteAddress().c_str(), request->message().c_str());          response->set_message("echo message: " + request->message());          done->Run();      }  };</code></pre>    <p>注意:</p>    <ul>     <li> <p>服务完成后必须调用done->Run(),通知RPC系统服务完成,触发发送Response;</p> </li>     <li> <p>在调了done->Run()之后,Echo的所有四个参数都不再能访问; done-Run()可以分派到其他线程中执行,以实现了真正的异步处理;</p> </li>    </ul>    <h3><strong>注册和启动服务</strong></h3>    <pre>  <code class="language-cpp">int main()  {      SOFA_PBRPC_SET_LOG_LEVEL(NOTICE);        sofa::pbrpc::RpcServerOptions options;      options.work_thread_num = 8;      sofa::pbrpc::RpcServer rpc_server(options);        if (!rpc_server.Start("0.0.0.0:12321")) {          SLOG(ERROR, "start server failed");          return EXIT_FAILURE;      }        sofa::pbrpc::test::EchoServer* echo_service = new EchoServerImpl();      if (!rpc_server.RegisterService(echo_service)) {          SLOG(ERROR, "register service failed");          return EXIT_FAILURE;      }      rpc_server.Run();      rpc_server.Stop();        return EXIT_SUCCESS;  }</code></pre>    <h2><strong>实现Client</strong></h2>    <p>Client支持同步和异步两种调用方式:</p>    <ul>     <li> <p>同步调用时,调用线程会被阻塞,直到收到回复或者超时;</p> </li>     <li> <p>异步调用时,调用线程不会被阻塞,收到回复或者超时会调用用户提供的回调函数;</p> </li>    </ul>    <h3><strong>头文件</strong></h3>    <pre>  <code class="language-cpp">#include <sofa/pbrpc/pbrpc.h>  // sofa-pbrpc头文件  #include "echo_service.pb.h"   // service接口定义头文件</code></pre>    <h3><strong>同步调用</strong></h3>    <pre>  <code class="language-cpp">int main()  {      SOFA_PBRPC_SET_LOG_LEVEL(NOTICE);      sofa::pbrpc::RpcClientOptions client_options;      client_options.work_thread_num = 8;      sofa::pbrpc::RpcClient rpc_client(client_options);      sofa::pbrpc::RpcChannel rpc_channel(&rpc_client, "127.0.0.1:12321");        sofa::pbrpc::test::EchoServer_Stub stub(&rpc_channel);        sofa::pbrpc::test::EchoRequest request;      request.set_message("Hello world!");      sofa::pbrpc::test::EchoResponse response;      sofa::pbrpc::RpcController controller;      controller.SetTimeout(3000);      stub.Echo(&controller, &request, &response, NULL);      if (controller.Failed()) {          SLOG(ERROR, "request failed: %s", controller.ErrorText().c_str());      }        return EXIT_SUCCESS;  }</code></pre>    <h3>异步调用</h3>    <pre>  <code class="language-cpp"> {          SLOG(ERROR, "request failed: %s", cntl->ErrorText().c_str());      }      else {          SLOG(NOTICE, "request succeed: %s", response->message().c_str());      }      delete cntl;      delete request;      delete response;        *callbacked = true;  }    int main()  {      SOFA_PBRPC_SET_LOG_LEVEL(NOTICE);        sofa::pbrpc::RpcClientOptions client_options;      sofa::pbrpc::RpcClient rpc_client(client_options);        sofa::pbrpc::RpcChannel rpc_channel(&rpc_client, "127.0.0.1:12321");      sofa::pbrpc::test::EchoServer_Stub stub(&rpc_channel);      sofa::pbrpc::test::EchoRequest* request = new sofa::pbrpc::test::EchoRequest();      request->set_message("Hello from qinzuoyan01");      sofa::pbrpc::test::EchoResponse* response = new sofa::pbrpc::test::EchoResponse();      sofa::pbrpc::RpcController* cntl = new sofa::pbrpc::RpcController();      cntl->SetTimeout(3000);      bool callbacked = false;      google::protobuf::Closure* done = sofa::pbrpc::NewClosure(              &EchoCallback, cntl, request, response, &callbacked);        stub.Echo(cntl, request, response, done);      while (!callbacked) {          usleep(100000);      }        return EXIT_SUCCESS;  }</code></pre>    <p>注意:</p>    <ul>     <li> <p>异步调用传入的controller、request、response参数,在回调函数执行之前需一直保持有效;</p> </li>     <li> <p>回调函数的执行会分配到专门的回调线程中运行,可以通过设置RpcClientOptions的callback <em>thread</em> num来配置回调线程数;</p> </li>    </ul>    <h2><strong>实现</strong></h2>    <h2><strong>系统结构</strong></h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/66599eae4851be559526a54db0aff333.png"></p>    <ul>     <li> <p>RpcClientStream/RpcServerStream:代表client和server之间的连接,用于client和server的网络通信。</p> </li>     <li> <p>ThreadGroup:client和server内部线程池,用于io操作和执行回调。</p> </li>     <li> <p>TimeoutManager:采用订阅者模型,对rpc请求进行超时管理。</p> </li>     <li> <p>RpCListenser:接受来自client的连接请求,创建与client之间的连接。</p> </li>     <li> <p>ServicePool:server端服务管理与路由。</p> </li>    </ul>    <p><img src="https://simg.open-open.com/show/742d3a6badbb336bd07b02b53a72dd23.png"></p>    <p>整个RPC调用经过以下阶段:</p>    <ul>     <li> <p>Stub调用RPC函数发起RPC请求.</p> </li>     <li> <p>RpcChannel调用CallMethod执行RPC调用。</p> </li>     <li> <p>RpcClient选取RpcClientStream异步发送请求,并添加至超时队列。</p> </li>     <li> <p>server端RpcListener接收到client的请求,创建对应RpcServerStream。</p> </li>     <li> <p>RpcServerStream接收数据,根据meta信息在ServerPool中选取对应Service.Method执行。</p> </li>     <li> <p>server通过RpcServerStream发送执行结果,回复过程与请求过程类似。</p> </li>    </ul>    <h2><strong>技术特点</strong></h2>    <h2><strong>协议栈方式的网络模型</strong></h2>    <p><img src="https://simg.open-open.com/show/f16e62ba363a18f2a6828073457923ac.png"></p>    <p>在sofa-pbrpc中网络数据自上而下流划分为RpcClientStream/RpcServerStream、RpcMessageStream、RpcByteStream三层。消息流层主要负责网络通信相关的操作,操作对象为序列化之后的二机制字节流;消息流层处理的对象是由header、meta和data组装的消息,负责消息级别的控制与统计;协议层负责异步发送接受请求和响应数据。三层结构每一层是下一层的封装和扩展,采用这样协议栈方式的层次划分更加有利于数据协议的扩展。</p>    <h2><strong>ZeroCopy方式管理缓冲区。</strong></h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/edc7d8a82c1a07b3fa63ef145501b128.png"></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/2ddde46fa8fba40456e07ecd6ffe070c.png"></p>    <p>sofa-pbrpc将内存划分为固定大小的buffer作为缓冲区,对buffer采用引用计数进行管理,减少不必要的内存拷贝。</p>    <h2><strong>支持HTTP协议</strong></h2>    <p>除了使用原生client访问server外,sofa-pbrpc也支持使用http协议访问server上的服务。同时,用户可以通过使用server端的WebService工具类,快速实现server的对于http请求的处理逻辑。</p>    <h2><strong>支持json格式数据传输</strong></h2>    <p>sofa-pbrpc支持用户使用http客户端向server发送json格式的数据请求,并返回json格式的响应。</p>    <h2><strong>提供丰富的工具类</strong></h2>    <p>sofa-pbrpc提供常用工具类给开发者,包括:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/2d4d2510ed148d59e75cb836cb588cee.png"></p>    <h2><strong>性能</strong></h2>    <h2><strong>测试环境</strong></h2>    <ul>     <li>cpu 16core</li>     <li>memory 64G</li>     <li>kernel 2.6.32_1-15-0-0</li>    </ul>    <h2><strong>吞吐</strong></h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a59b26ae549113f49d297d5f9e6c7588.jpg"></p>    <h2><strong>延迟</strong></h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/7bd1dc7aa10d5f4d73563589be713210.jpg"></p>    <p> </p>    <p> </p>    <p>来自:https://my.oschina.net/u/1050511/blog/751841</p>    <p> </p>