brpc安装及使用

目录

一.介绍

二.安装

三.类与接口介绍

日志输出类与接口

protobuf类与接口

服务端类与接口

客户端类与接口:

四.使用

同步调用

异步调用

五.封装思想

 六.封装类使用测试

六.总结


一.介绍

        brpc 是用 c++语言编写的工业级 RPC 框架,常用于搜索、存储、机器学习、广告、推荐等高性能系统。

你可以使用它:

搭建能在一个端口支持多协议的服务, 或访问各种服务

  • restful http/https, h2/gRPC。使用 brpc http 实现比 libcurl 方便多了。从其他语言通过 HTTP/h2+json 访问基于 protobuf 的协议。
  • redis memcached, 线程安全,比官方 client 更方便。
  • rtmp/flv/hls, 可用于搭建流媒体服务
  • 支持 thrift , 线程安全,比官方 client 更方便。
  • 各种百度内使用的协议: baidu_std, streaming_rpc, hulu_pbrpc, sofa_pbrpc, nova_pbrpc, public_pbrpc, ubrpc 和使用 nshead 的各种协议。
  • 基于工业级的 RAFT 算法实现搭建高可用分布式系统,已在 braft 开源。

Server 同步异步处理请求。

Client 支持同步异步半同步,或使用组合 channels 简化复杂的分库或并发访问。

• 通过 http 界面调试服务, 使用 cpu, heap, contention profilers。

获得更好的延时和吞吐

把你组织中使用的协议快速地加入 brpc,或定制各类组件, 包括命名服务 (dns, zk, etcd), 负载均衡 (rr, random, consistent hashing)。

对比其他 rpc 框架

二.安装

先安装依赖

sudo apt-get install -y git g++ make libssl-dev libprotobuf-dev libprotoc-dev protobuf-compiler libleveldb-dev

 安装 brpc

git clone https://github.com/apache/brpc.git
cd brpc/
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr .. && cmake --build . -j6
make && sudo make install

三.类与接口介绍

日志输出类与接口

包含头文件:

#include <butil/logging.h>

        日志输出这里,本质上我们其实用不着 brpc 的日志输出,因此在这里主要介绍如何关闭日志输出。

namespace logging 
{
    enum LoggingDestination 
    {
        LOG_TO_NONE = 0
    };
    struct BUTIL_EXPORT LoggingSettings 
    {
        LoggingSettings();
        LoggingDestination logging_dest;
    };
    bool InitLogging(const LoggingSettings& settings);
};

        BUTIL_EXPORT是宏(用于符号导出 / 导入等逻辑)。

protobuf类与接口

namespace google 
{
namespace protobuf 
{
    class PROTOBUF_EXPORT Closure 
    {
    public:
        Closure() {}
        virtual ~Closure();
        virtual void Run() = 0;
};
    inline Closure* NewCallback(void (*function)());
    class PROTOBUF_EXPORT RpcController 
    {
        bool Failed();
        std::string ErrorText() ;
    } 
};
};

服务端类与接口

        这里只介绍主要用到的成员与接口。

namespace brpc 
{
    struct ServerOptions 
    {
        //无数据传输,则指定时间后关闭连接
        int idle_timeout_sec; // Default: -1 (disabled)
        int num_threads; // Default: #cpu-cores
        //....
    };
    enum ServiceOwnership 
    {
        //添加服务失败时,服务器将负责删除服务对象
        SERVER_OWNS_SERVICE,
        //添加服务失败时,服务器也不会删除服务对象
        SERVER_DOESNT_OWN_SERVICE
    };
    class Server 
    {
        int AddService(google::protobuf::Service* service,
        ServiceOwnership ownership);
        int Start(int port, const ServerOptions* opt);
        int Stop(int closewait_ms/*not used anymore*/);
        int Join();
        //休眠直到 ctrl+c 按下,或者 stop 和 join 服务器
        void RunUntilAskedToQuit();
    };
    class ClosureGuard 
    {
        explicit ClosureGuard(google::protobuf::Closure* done);
        ~ClosureGuard() { if (_done) _done->Run(); }
    };
    class HttpHeader 
    {
        void set_content_type(const std::string& type)
        const std::string* GetHeader(const std::string& key)
        void SetHeader(const std::string& key, 
        const std::string& value);
        const URI& uri() const { return _uri; }
        HttpMethod method() const { return _method; }
        void set_method(const HttpMethod method)
        int status_code()
        void set_status_code(int status_code);
    };
    class Controller : public google::protobuf::RpcController 
    {
        void set_timeout_ms(int64_t timeout_ms);
        void set_max_retry(int max_retry);
        google::protobuf::Message* response();
        HttpHeader& http_response();
        HttpHeader& http_request();
        bool Failed();
        std::string ErrorText();
 
        using AfterRpcRespFnType = std::function<
        void(Controller* cntl,
        const google::protobuf::Message* req,
        const google::protobuf::Message* res)>;
        void set_after_rpc_resp_fn(AfterRpcRespFnType&& fn)
    };
};

客户端类与接口:

namespace brpc 
{
    struct ChannelOptions 
    {
        //请求连接超时时间
        int32_t connect_timeout_ms;// Default: 200 (milliseconds)
        //rpc 请求超时时间
        int32_t timeout_ms;// Default: 500 (milliseconds)
        //最大重试次数
        int max_retry;// Default: 3
        //序列化协议类型 options.protocol = "baidu_std";
        AdaptiveProtocolType protocol;
        //....
    };
    class Channel : public ChannelBase 
    {
        //初始化接口,成功返回 0;
        int Init(const char* server_addr_and_port, 
        const ChannelOptions* options);
    };
};

四.使用

同步调用

        同步调用是指客户端会阻塞收到 server 端的响应或发生错误。

        下面我们以 Echo(输出 hello world)方法为例, 来讲解基础的同步 RPC 请求是如何实现的。

创建 proto 文件 - main.proto:

syntax="proto3";
package example;

//给 Protobuf C++ 代码生成器的 “开关”,决定是否为 service 生成通用服务基类 / 存根代码
option cc_generic_services = true;

//定义Echo方法请求参数结构
message EchoRequest
{
    string message = 1;
};

//定义Echo方法响应参数结构
message EchoResponse
{
    string message = 1;
};

//定义RPC远端方法
service EchoService
{
    rpc Echo(EchoRequest) returns (EchoResponse);
};

服务端源码:server.cc

#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
#include "main.pb.h"

//使用gflags定义一些命令行参数
DEFINE_int32(listen_port, 8080, "服务器监听端口");
DEFINE_int32(idle_timeout_s, -1, "空闲连接超时关闭时间:默认-1表示不关闭");
DEFINE_int32(thread_count, 3, "服务器启动线程数量");

namespace example
{
    class EchoServiceImpl : public EchoService
    {
    public:
        EchoServiceImpl()
        {}
        ~EchoServiceImpl()
        {}
        void Echo(google::protobuf::RpcController* controller,
                       const ::example::EchoRequest* request,
                       ::example::EchoResponse* response,
                       ::google::protobuf::Closure* done)
        {
            brpc::ClosureGuard rpc_guard(done);
            std::cout << "收到消息:" << request->message() << std::endl;

            std::string str = request->message() + "--这是响应!!";
            response->set_message(str);
        }
    };
};

int main(int argc, char* argv[])
{
    //关闭brpc日志输出
    logging::LoggingSettings log_setting;
    log_setting.logging_dest = logging::LoggingDestination::LOG_TO_NONE;
    logging::InitLogging(log_setting);
    //解析命令行参数
    google::ParseCommandLineFlags(&argc, &argv, true);
    //定义服务器
    brpc::Server server;

    //创建服务对象
    example::EchoServiceImpl echo_service_impl;

    //将服务添加到服务器中
    if(server.AddService(&echo_service_impl, brpc::SERVER_DOESNT_OWN_SERVICE) != 0)
    {
        std::cout << "add service failed!\n";
        return -1;
    }
    //开始运作服务器
    brpc::ServerOptions options;
    options.idle_timeout_sec = FLAGS_idle_timeout_s;
    options.num_threads = FLAGS_thread_count;
    if(server.Start(FLAGS_listen_port, &options) != 0)
    {
        std::cout << "failed to start EchoServer";
        return -1;
    }

    //阻塞等待服务端运行
    server.RunUntilAskedToQuit();
    return 0;
}

客户端源码:client.cc

#include <gflags/gflags.h>
#include <butil/logging.h>
#include <butil/time.h> // ???
#include <brpc/channel.h>
#include <iostream>
#include "main.pb.h"

DEFINE_string(protocol, "baidu_std", "通信协议类型,默认使用brpc自定制协议");
DEFINE_string(server_host, "127.0.0.1:8080", "服务器地址信息");
DEFINE_int32(timeout_ms, 500, "Rpc 请求超时时间-毫秒");
DEFINE_int32(max_retry, 3, "请求重试次数");

int main(int argc, char *argv[])
{
    google::ParseCommandLineFlags(&argc, &argv, true);
    // 创建通道,可以理解为客户端到服务器的一条通信线路
    brpc::Channel channel;
    // 初始化通道,NULL 表示使用默认选项
    brpc::ChannelOptions options;
    options.protocol = FLAGS_protocol;
    options.timeout_ms = FLAGS_timeout_ms;
    options.max_retry = FLAGS_max_retry;
    if (channel.Init(FLAGS_server_host.c_str(), &options) != 0)
    {
        LOG(ERROR) << "Fail to initialize channel";
        return -1;
    }
    // 通常,我们不应直接调用通道,而是包装它的stub服务,通过 stub 进行rpc调用
    example::EchoService_Stub stub(&channel);
    
    while(true)
    {
        // 创建请求、响应、控制对象
        example::EchoRequest request;
        example::EchoResponse response;
        brpc::Controller cntl;
        // 构造请求响应
        std::string s;
        std::cin >> s;
        request.set_message(s);
        // 由于“done”(最后一个参数)为 NULL,表示阻塞等待响应
        stub.Echo(&cntl, &request, &response, NULL);
        if (cntl.Failed())
        {
            std::cout << "请求失败: " << cntl.ErrorText() << std::endl;
            return -1;
        }
        std::cout << "响应:" << response.message() << std::endl;
    }
    return 0;
}

编写makefile:

all : server client
server : server.cc main.pb.cc
	g++ -std=c++17 $^ -o $@ -lbrpc -lgflags -lssl -lcrypto -lleveldb -lpthread -lprotobuf
client : client.cc main.pb.cc
	g++ -std=c++17 $^ -o $@ -lbrpc -lgflags -lssl -lcrypto -lleveldb -lpthread -lprotobuf
%.pb.cc : %.proto
	protoc --cpp_out=./ $<

异步调用

        异步调用是指客户端注册一个响应处理回调函数, 当调用一个 RPC 接口时立即返回,不会阻塞等待响应, 当 server 端返回响应时会调用传入的回调函数处理响应。

五.封装思想

        rpc 调用这里的封装,因为不同的服务调用使用的是不同的 Stub,这个封装起来的意义不大,因此我们只需要封装通信所需的 Channel 管理即可,这样当需要进行什么样的服务调用的时候,只需要通过服务名称获取对应的 channel,然后实例化 Stub 进行调用即可。

封装 Channel 的管理,每个不同的服务可能都会有多个主机提供服务,因此一个服务可能会对应多个 Channel,需要将其管理起来,并提供获取指定服务 channel 的接口

  • 进行 rpc 调用时,获取 channel,目前以 RR 轮转的策略选择 channel

提供进行服务声明的接口,因为在整个系统中,提供的服务有很多,但是当前可能并不一定会用到所有的服务,因此通过声明来告诉模块哪些服务是自己关心的,需要建立连接管理起来,没有添加声明的服务即使上线也不需要进行连接的建立。

提供服务上线时的处理接口,也就是新增一个指定服务的 channel。

提供服务下线时的处理接口,也就是删除指定服务下的指定 channel。

封装代码:

#pragma once
#include <brpc/channel.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include "logger.hpp"

namespace fish
{
    // 1. 封装单个服务的信道管理类:
    class ServiceChannel
    {
    public:
        using ptr = std::shared_ptr<ServiceChannel>;
        using ChannelPtr = std::shared_ptr<brpc::Channel>;
        ServiceChannel(const std::string &name) : _service_name(name), _index(0) {}
        // 服务上线了一个节点,则调用append新增信道
        void append(const std::string &host)
        {
            ChannelPtr channel = std::make_shared<brpc::Channel>();
            brpc::ChannelOptions options;
            options.connect_timeout_ms = -1;
            options.timeout_ms = -1;
            options.max_retry = 3;
            options.protocol = "baidu_std";
            int ret = channel->Init(host.c_str(), &options);
            if (ret == -1)
            {
                LOG_ERROR("初始化{}-{}信道失败!", _service_name, host);
                return;
            }
            std::unique_lock<std::mutex> lock(_mutex);
            _hosts.insert(std::make_pair(host, channel));
            _channels.push_back(channel);
        }
        // 服务下线了一个节点,则调用remove释放信道
        void remove(const std::string &host)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _hosts.find(host);
            if (it == _hosts.end())
            {
                LOG_WARN("{}-{}节点删除信道时,没有找到信道信息!", _service_name, host);
                return;
            }
            for (auto vit = _channels.begin(); vit != _channels.end(); ++vit)
            {
                if (*vit == it->second)
                {
                    _channels.erase(vit);
                    break;
                }
            }
            _hosts.erase(it);
        }
        // 通过RR轮转策略,获取一个Channel用于发起对应服务的Rpc调用
        ChannelPtr choose()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_channels.size() == 0)
            {
                LOG_ERROR("当前没有能够提供 {} 服务的节点!", _service_name);
                return ChannelPtr();
            }
            int32_t idx = _index++ % _channels.size();
            return _channels[idx];
        }

    private:
        std::mutex _mutex;
        int32_t _index;                                     // 当前轮转下标计数器
        std::string _service_name;                          // 服务名称
        std::vector<ChannelPtr> _channels;                  // 当前服务对应的信道集合
        std::unordered_map<std::string, ChannelPtr> _hosts; // 主机地址与信道映射关系
    };
    // 总体的服务信道管理类
    class ServiceManager
    {
    public:
        using ptr = std::shared_ptr<ServiceManager>;
        ServiceManager() {}
        // 获取指定服务的节点信道
        ServiceChannel::ChannelPtr choose(const std::string &service_name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto sit = _services.find(service_name);
            if (sit == _services.end())
            {
                LOG_ERROR("当前没有能够提供 {} 服务的节点!", service_name);
                return ServiceChannel::ChannelPtr();
            }
            return sit->second->choose();
        }
        // 先声明,我关注哪些服务的上下线,不关心的就不需要管理了
        void declared(const std::string &service_name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _follow_services.insert(service_name);
        }
        // 服务上线时调用的回调接口,将服务节点管理起来
        void onServiceOnline(const std::string &service_instance, const std::string &host)
        {
            std::string service_name = getServiceName(service_instance);
            ServiceChannel::ptr service;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto fit = _follow_services.find(service_name);
                if (fit == _follow_services.end())
                {
                    LOG_DEBUG("{}-{} 服务上线了,但是当前并不关心!", service_name, host);
                    return;
                }
                // 先获取管理对象,没有则创建,有则添加节点
                auto sit = _services.find(service_name);
                if (sit == _services.end())
                {
                    service = std::make_shared<ServiceChannel>(service_name);
                    _services.insert(std::make_pair(service_name, service));
                }
                else
                {
                    service = sit->second;
                }
            }
            if (!service)
            {
                LOG_ERROR("新增 {} 服务管理节点失败!", service_name);
                return;
            }
            service->append(host);
            LOG_DEBUG("{}-{} 服务上线新节点,进行添加管理!", service_name, host);
        }
        // 服务下线时调用的回调接口,从服务信道管理中,删除指定节点信道
        void onServiceOffline(const std::string &service_instance, const std::string &host)
        {
            std::string service_name = getServiceName(service_instance);
            ServiceChannel::ptr service;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto fit = _follow_services.find(service_name);
                if (fit == _follow_services.end())
                {
                    LOG_DEBUG("{}-{} 服务下线了,但是当前并不关心!", service_name, host);
                    return;
                }
                // 先获取管理对象,没有则创建,有则添加节点
                auto sit = _services.find(service_name);
                if (sit == _services.end())
                {
                    LOG_WARN("删除{}服务节点时,没有找到管理对象", service_name);
                    return;
                }
                service = sit->second;
            }
            service->remove(host);
            LOG_DEBUG("{}-{} 服务下线节点,进行删除管理!", service_name, host);
        }

    private:
        std::string getServiceName(const std::string &service_instance)
        {
            auto pos = service_instance.find_last_of('/');
            if (pos == std::string::npos)
                return service_instance;
            return service_instance.substr(0, pos);
        }

    private:
        std::mutex _mutex;
        std::unordered_set<std::string> _follow_services;               // 关心的服务集合
        std::unordered_map<std::string, ServiceChannel::ptr> _services; // 服务名和这个服务的管理对象
    };
};

 六.封装类使用测试

add.proto:

syntax="proto3";
package example;

option cc_generic_services = true;

message AddRequest
{
    int64 x = 1;
    int64 y = 2;
};

message AddResponse
{
    int64 result = 1;
};

service AddService
{
    rpc Echo(AddRequest) returns (AddResponse);
};

client.cc:

#include "../../../common/channel.hpp"
#include "../../../common/etcd.hpp"
#include "add.pb.h"

int main(int argc, char *argv[])
{
    // 关闭brpc日志输出
    logging::LoggingSettings log_setting;
    log_setting.logging_dest = logging::LoggingDestination::LOG_TO_NONE;
    logging::InitLogging(log_setting);

    fish::init_logger(0, "", 0);
    fish::ServiceManager sm;
    sm.declared("/service/user");

    auto put_cb = std::bind(&fish::ServiceManager::onServiceOnline, &sm, std::placeholders::_1, std::placeholders::_2);
    auto del_cb = std::bind(&fish::ServiceManager::onServiceOffline, &sm, std::placeholders::_1, std::placeholders::_2);

    // fish::Discovery dis("http://127.0.0.1:2379", "/service/user", &fish::ServiceManager::onServiceOnline, &fish::ServiceManager::onServiceOffline);
    fish::Discovery dis("http://127.0.0.1:2379", "/service/user", put_cb, del_cb);

    auto channel = sm.choose("/service/user");

    example::AddService_Stub stub(channel.get());
    while (true)
    {
        brpc::Controller cntl;
        example::AddRequest request;
        int x, y;
        std::cin >> x;
        std::cin >> y;
        request.set_x(x);
        request.set_y(y);
        example::AddResponse response;
        stub.Echo(&cntl, &request, &response, nullptr);
        std::cout << "计算结果是:" << response.result() << std::endl;
    }

    return 0;
}

 server.cc:

#include "../../../common/etcd.hpp"
#include "add.pb.h"
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>

DEFINE_int32(port, 8080, "提供服务的端口");
DEFINE_string(host, "127.0.0.1", "服务注册ip");

// 重写rpc调用方法
class AddServiceImp : public example::AddService
{
    void Echo(google::protobuf::RpcController *controller,
              const ::example::AddRequest *request,
              ::example::AddResponse *response,
              ::google::protobuf::Closure *done)
    {
        brpc::ClosureGuard rpc_guard(done);
        response->set_result(request->x() + request->y());
        LOG_DEBUG("收到请求,两个数分别是{}和{}", request->x(), request->y());
    }
};

int main(int argc, char *argv[])
{
    // 关闭brpc日志输出
    logging::LoggingSettings log_setting;
    log_setting.logging_dest = logging::LoggingDestination::LOG_TO_NONE;
    logging::InitLogging(log_setting);

    fish::init_logger(0, "", 0);
    google::ParseCommandLineFlags(&argc, &argv, true);

    fish::Registry etcdReg("127.0.0.1:2379");                      // 服务注册对象
    etcdReg.registry("/service/user/instance1", "127.0.0.1:8080"); // 注册

    // 定义服务器
    brpc::Server server;

    // 创建服务对象
    AddServiceImp add_service_impl;

    // 将服务添加到服务器中
    if (server.AddService(&add_service_impl, brpc::SERVER_DOESNT_OWN_SERVICE) != 0)
    {
        std::cout << "add service failed!\n";
        return -1;
    }
    // 开始运作服务器
    brpc::ServerOptions options;
    options.idle_timeout_sec = -1;
    options.num_threads = 1;
    if (server.Start(FLAGS_port, &options) != 0)
    {
        std::cout << "failed to start EchoServer";
        return -1;
    }

    // 阻塞等待服务端运行
    server.RunUntilAskedToQuit();

    return 0;
}

运行展示:

六.总结

        本篇博客是关于brpc的安装与使用,供大家和本人在日常开发中进行参考使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值