• 【brpc学习案例实践一】rpc服务构造基本流程


    前言

    在c++rpc框架中,brpc简直越用越爽,平时工作中也常用到brpc,一直没来得及总结,抽空写点,也供自己查阅用。下附几个常用学习地址:
    brpc官网开源地址:
    https://github.com/luozesong/brpc/blob/master/docs/cn/redis_client.md
    protobuf官方文档:
    https://protobuf.dev/programming-guides/proto2/

    brpc使用流程

    1、定义proto

    一般包含request、response、echoservice三种proto,echoservice在里面会定义一个rpc(stub)桩函数,proto编译器会生成一个与定义的stub接口同名的抽象接口,在用户只需要继承echoservice实现自己的service类之后,重写该stub函数即可。如果不定义自己的echoservice接口,一些场景也可使用brpc内部已经定义好的service接口,像nsheadservice,用户直接继承该类,实现自己的ProcessNsheadRequest()接口函数即可。
    下面来看两种proto定义的例子:

    • 自定义rpc service
    # Tell protoc to generate base classes for C++ Service. modify to java_generic_services or py_generic_services for java or python. 
    option cc_generic_services = true;
    
    message EchoRequest {
          required string message = 1;
    };
    message EchoResponse {
          required string message = 1;
    };
    service EchoService {
          rpc Echo(EchoRequest) returns (EchoResponse);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面我们在proto里面定义了EchoService服务,我们在下面定义自己的MyEchoService继承该EchoService类,重写proto自动编译生成的抽象接口Echo即可。

    class MyEchoService : public EchoService  {
    public:
        void Echo(::google::protobuf::RpcController* cntl_base,
                  const ::example::EchoRequest* request,
                  ::example::EchoResponse* response,
                  ::google::protobuf::Closure* done) {
            // This RAII object calls done->Run() automatically at exit.
            brpc::ClosureGuard done_guard(done);
             
            brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
     
            // fill response
            response->set_message(request->message());
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    继承brpc内部service接口(以nshead为例):

    class Fw2NsheadService : public ::baidu::rpc::NsheadService {
    public:
        Fw2NsheadService(App* app);
    
        virtual ~Fw2NsheadService();
    
        void ProcessNsheadRequest(const baidu::rpc::Server& server,
                                  baidu::rpc::Controller* cntl,
                                  const baidu::rpc::NsheadMessage& request,
                                  baidu::rpc::NsheadMessage* response,
                                  baidu::rpc::NsheadClosure* done) {
       bus::log::LogClosureGuard done_guard(cntl, done);
    
        int ret = HTTP_STATUS_SERVER_ERROR;
        if (cntl->Failed()) {
            LOG(FATAL) << "Controller Failed Before Process, Reason:" << cntl->ErrorText();
            BUS_SET_ERRNO(ret);
            return;
        }
    
        ret = _app->Execute(request.body, response->body);
        BUS_SET_ERRNO(ret);                  
    }
    
    private:
        App* _app;    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2、proto定义好之后,需要实现生成的service接口(上面已讲过)

    需实现自己的service响应函数。另外我们通常需要在自己的sevice函数中初始化一个done_guard或自己手动调用done->run():
    brpc::ClosureGuard done_guard(done);
    done由框架创建,递给服务回调,包含了调用服务回调后的后续动作,包括检查response正确性,序列化,打包,发送等逻辑。done_guard就是为了保证退出服务自动调用,释放资源。如果我们需要实现异步服务,除了手动调用done->run()外,还可以使用done_guard.release()

    3、定义服务对象brpc::Server server;

    默认构造后的Server不包含任何服务,也不会对外提供服务,仅仅是一个对象。

    4、给服务对象添加服务实例

    通过如下方法插入你的Service实例。

    int AddService(google::protobuf::Service* service, ServiceOwnership ownership);
    
    • 1

    若ownership参数为SERVER_OWNS_SERVICE,Server在析构时会一并删除Service,意味着我们自己定义的服务实例也会被删除,如果还需要该service实例,应设为SERVER_DOESNT_OWN_SERVICE。

    插入MyEchoService代码如下:

    brpc::Server server;
    MyEchoService my_echo_service;
    if (server.AddService(&my_echo_service, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
        LOG(FATAL) << "Fail to add my_echo_service";
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5、启动服务

    brpc::ServerOptions options;  // 包含了默认值
    options.xxx = yyy;
    ...
    server.Start(..., &options);
    
    • 1
    • 2
    • 3
    • 4

    start的接口有多种:

    int Start(const char* ip_and_port_str, const ServerOptions* opt);
    int Start(EndPoint ip_and_port, const ServerOptions* opt);
    int Start(int port, const ServerOptions* opt);
    int Start(const char *ip_str, PortRange port_range, const ServerOptions *opt);  // r32009后增加
    
    • 1
    • 2
    • 3
    • 4

    options为NULL时所有参数取默认值。一个服务只能监听一个端口,如果要监听多个端口需要起多个服务。

    6、停止服务

    server.Stop(closewait_ms); // closewait_ms实际无效,出于历史原因未删
    server.Join();
    
    • 1
    • 2

    Stop()不会阻塞,Join()会。分成两个函数的原因在于当多个Server需要退出时,可以先全部Stop再一起Join,如果一个个Stop/Join,可能得花费Server个数倍的等待时间。

    不管closewait_ms是什么值,server在退出时会等待所有正在被处理的请求完成,同时对新请求立刻回复ELOGOFF错误以防止新请求加入。这么做的原因在于只要server退出时仍有处理线程运行,就有访问到已释放内存的风险。如果你的server“退不掉”,很有可能是由于某个检索线程没结束或忘记调用done了。

    当client看到ELOGOFF时,会跳过对应的server,并在其他server上重试对应的请求。所以在一般情况下brpc总是“优雅退出”的,重启或上线时几乎不会或只会丢失很少量的流量。

    RunUntilAskedToQuit()函数可以在大部分情况下简化server的运转和停止代码。在server.Start后,只需如下代码即会让server运行直到按到Ctrl-C。

    // Wait until Ctrl-C is pressed, then Stop() and Join() the server.
    server.RunUntilAskedToQuit();
     
    // server已经停止了,这里可以写释放资源的代码。
    
    • 1
    • 2
    • 3
    • 4

    Join()完成后可以修改其中的Service,并重新Start。

  • 相关阅读:
    居民消费价格指数变化新鲜出炉,这类商品同比涨幅最大
    Qt窗口无标题栏拖动放大
    记录一下Kubernetes的快速入门
    06、SpringBoot+微信支付 -->商户定时查订单状态、用户取消订单(关闭订单API)、查询订单API--到微信支付平台查询订单
    threejs
    【javase基础】第十八篇(项目):开发团队调度软件
    257. 关押罪犯 - AcWing题库 【细节二分 | 细节二分图】
    Docker consul的容器服务更新与发现
    基于springboot的小说阅读网站设计与实现【附源码】
    Java易错知识点整理(待更新)
  • 原文地址:https://blog.csdn.net/qq_42936727/article/details/134474457