cornerstone中rpc通信源码解析

1.概述

asio_service是cornerstone中实现各种服务的工具,rpc通信属于其中的一部分,rpc通信主要由三个部分组成:
asio_rpc_clientasio_rpc_listenerasio_rpc_session
cornerstone中的rpc并没有采用protobuf,通信是通过stand-alone的asio库中的socket进行的。

2.rpc通信中的各部分关系图


如图rpc通信类似于client-server的关系,每一个listener-client通信都得走asio_rpc_session,实现逻辑上的连接与通信解耦。

3.asio_rpc_client代码分析

3.1 asio_rpc_client初始化

class asio_rpc_client : public rpc_client, public std::enable_shared_from_this<asio_rpc_client>
{
public:
    asio_rpc_client(asio::io_service& io_svc, std::string& host, std::string& port)
        : resolver_(io_svc), socket_(io_svc), host_(host), port_(port)
    {
    }
    virtual ~asio_rpc_client()
    {
        if (socket_.is_open())
        {
            socket_.close();
        }
    }

这里构造函数与析构函数都很简单。
重要的是两点。
1.这里有一个异步操作的技巧: public std::enable_shared_from_this<asio_rpc_client>
与之搭配的是后面的语句 ptr<asio_rpc_client> self(this->shared_from_this());(ptr是shared_ptr),
这句话的作用是允许一个类的实例安全地生成指向自身的 std::shared_ptr。

一般的生成指向A类实例自身的shared_ptr我们可能会写成ptr<A>(this),但这会有问题。
cppreference上关于shared_ptr的解释
cppreference里面说了

Constructing a std::shared_ptr for an object that is already managed by another std::shared_ptr will not consult weak_this and thus will lead to undefined behavior.

表明用shared_ptr去管理一个已经被另一个shared_ptr管理的object不会增加引用计数,从而导致未定义行为。
所以异步操作的函数(对asio_rpc_client是send()),每次调用ptr<A>(this),并不会使不同shared_ptr引用计数正确增加,从而因计数错误而导致double free,进而崩溃。

2.这里用到了抽象类的技巧,rpc_client是cornerstone定义的一个抽象类,具体代码如下

  using rpc_result = async_result<ptr<resp_msg>, ptr<rpc_exception>>;
  using rpc_handler = rpc_result::handler_type ;

    class rpc_client {
    __interface_body__(rpc_client)
    public:
        virtual void send(ptr<req_msg>& req, rpc_handler& when_done) = 0;
    };
  • 在__interface_body__宏定义里面定义了基本的构造与析构函数,同时运用了__nocopy__实现单例模式。
  • virtual void send(ptr<req_msg>& req, rpc_handler& when_done) = 0;使用纯虚函数,使得继承rpc_client的子类必须提供send这个虚函数的实现,进而为接口提供一个规范。

3.2 connect函数

void connected(
        ptr<req_msg>& req,
        rpc_handler& when_done,
        std::error_code err,
        asio::ip::tcp::resolver::iterator /* itor */)
    {
        if (!err)
        {
            this->send(req, when_done);
        }
        else
        {
            ptr<resp_msg> rsp;
            ptr<rpc_exception> except(
                cs_new<rpc_exception>(sstrfmt("failed to connect to remote socket %d").fmt(err.value()), req));
            when_done(rsp, except);
        }
    }

req: client要发送的request_msg的ptr指针
when_done: 异步操作中的回调函数
err: 错误码
itor: 是asio中对域名解析得到的ip地址列表

  • 如果没有err,则正常发送req
  • 如果出现了err,则抛出except,直接调用when_done(rsp,except)

(cs_new<T>即为make_shared<T>)

3.3 send函数

 public:
        virtual void send(ptr<req_msg>& req, rpc_handler& when_done) __override__ {
            ptr<asio_rpc_client> self(this->shared_from_this());
            if (!socket_.is_open()) {
                asio::ip::tcp::resolver::query q(host_, port_, asio::ip::tcp::resolver::query::all_matching);
                resolver_.async_resolve(q, [self, this, req, when_done](std::error_code err, asio::ip::tcp::resolver::iterator itor) -> void {
                    if (!err) {
                        asio::async_connect(socket_, itor, std::bind(&asio_rpc_client::connected, self, req, when_done, std::placeholders::_1, std::placeholders::_2));
                    }
                    else {
                        ptr<resp_msg> rsp;
                        ptr<rpc_exception> except(cs_new<rpc_exception>(lstrfmt("failed to resolve host %s due to error %d").fmt(host_.c_str(), err.value()), req));
                        when_done(rsp, except);
                    }
                });
            } else {
                // serialize req, send and read response
                std::vector<bufptr> log_entry_bufs;
                int32 log_data_size(0);
                for (std::vector<ptr<log_entry>>::const_iterator it = req->log_entries().begin();
                    it != req->log_entries().end(); 
                    ++it) {
                    bufptr entry_buf(buffer::alloc(8 + 1 + 4 + (*it)->get_buf().size()));
                    entry_buf->put((*it)->get_term());
                    entry_buf->put((byte)((*it)->get_val_type()));
                    entry_buf->put((int32)(*it)->get_buf().size());
                    (*it)->get_buf().pos(0);
                    entry_buf->put((*it)->get_buf());
                    entry_buf->pos(0);
                    log_data_size += (int32)entry_buf->size();
                    log_entry_bufs.emplace_back(std::move(entry_buf));
                }

                bufptr req_buf(buffer::alloc(RPC_REQ_HEADER_SIZE + log_data_size));
                req_buf->put((byte)req->get_type());
                req_buf->put(req->get_src());
                req_buf->put(req->get_dst());
                req_buf->put(req->get_term());
                req_buf->put(req->get_last_log_term());
                req_buf->put(req->get_last_log_idx());
                req_buf->put(req->get_commit_idx());
                req_buf->put(log_data_size);
                for (auto& item : log_entry_bufs) {
                    req_buf->put(*item);
                }

                req_buf->pos(0);
                auto buffer = asio::buffer(req_buf->data(), req_buf->size());
                asio::async_write(socket_, std::move(buffer), [self, req_msg = req, when_done, buf = std::move(req_buf)](std::error_code err, size_t bytes_transferred) mutable -> void
                {
                    self->sent(req_msg, buf, when_done, err, bytes_transferred);
                });
            }
        }
  • 首先为了在异步操作中保证这个实例不失活,使用 ptr<asio_rpc_client> self(this->shared_from_this());,并在async_writeasync_resolve的lambda函数体里面捕获self。
  • 判断socket是否open,如果没有,则先连接。
  • 如果socket已经open,则先序列化req,为正式发送做好准备。
    这里需要重点关注怎么序列化req的,也就是怎么将req变成一个buffer。

req的组成是 (val_type,src,dst,term,last_log_term,last_log_index,commit_idx,data_size,entry)
其中entry又是由 (term,val_type,entry_data_size,entry_data)
在序列化的时候,我们按entry的组成逐个塞入数据

  bufptr entry_buf(buffer::alloc(8 + 1 + 4 + (*it)->get_buf().size()));
                    entry_buf->put((*it)->get_term());
                    entry_buf->put((byte)((*it)->get_val_type()));
                    entry_buf->put((int32)(*it)->get_buf().size());
                    (*it)->get_buf().pos(0);
                    entry_buf->put((*it)->get_buf());
                    entry_buf->pos(0);
                    log_data_size += (int32)entry_buf->size();
                    log_entry_bufs.emplace_back(std::move(entry_buf));
  • 给req塞入消息头,同时塞入entry。
 bufptr req_buf(buffer::alloc(RPC_REQ_HEADER_SIZE + log_data_size));
                req_buf->put((byte)req->get_type());
                req_buf->put(req->get_src());
                req_buf->put(req->get_dst());
                req_buf->put(req->get_term());
                req_buf->put(req->get_last_log_term());
                req_buf->put(req->get_last_log_idx());
                req_buf->put(req->get_commit_idx());
                req_buf->put(log_data_size);
                for (auto& item : log_entry_bufs) {
                    req_buf->put(*item);
                }
  • 发送消息
  req_buf->pos(0);
                auto buffer = asio::buffer(req_buf->data(), req_buf->size());
                asio::async_write(socket_, std::move(buffer), [self, req_msg = req, when_done, buf = std::move(req_buf)](std::error_code err, size_t bytes_transferred) mutable -> void
                {
                    self->sent(req_msg, buf, when_done, err, bytes_transferred);
                });

4.asio_rpc_listener代码分析

4.1 初始化

 class asio_rpc_listener : public rpc_listener, public std::enable_shared_from_this<asio_rpc_listener> {
    public:
        asio_rpc_listener(asio::io_service& io, ushort port, ptr<logger>& l) 
            : io_svc_(io), handler_(), acceptor_(io, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)), active_sessions_(), session_lock_(), l_(l) {}
        __nocopy__(asio_rpc_listener)

    public:
        virtual void stop() override {
            acceptor_.close();
        }

        virtual void listen(ptr<msg_handler>& handler) override {
            handler_ = handler;
            start();
        }   

这里初始化比较简单,重点关注asio_rpc_listener是怎么提供接口的。
逻辑上来说listener对外只需提供listen stop接口即可,所以我们将他们声明为public,
对于具体的实现,不需要向外界展示,则声明为private,这样可以使接口层次清晰。

类似asio_rpc_client,asio_rpc_listener也是继承自抽象类rpc_listener。

namespace cornerstone {
    // for backward compatibility
    class raft_server;
    typedef raft_server msg_handler;

    class rpc_listener {
    __interface_body__(rpc_listener)
    public:
        virtual void listen(ptr<msg_handler>& handler) = 0;
        virtual void stop() = 0;
    };
}

4.2 start函数

      void start() {
            if (!acceptor_.is_open()) {
                return;
            }

            ptr<asio_rpc_listener> self(this->shared_from_this());
            ptr<rpc_session> session(cs_new<rpc_session>(io_svc_, handler_, l_, std::bind(&asio_rpc_listener::remove_session, self, std::placeholders::_1)));
            acceptor_.async_accept(session->socket(), [self, this, session](const asio::error_code& err) -> void {
                if (!err) {
                    this->l_->debug("receive a incoming rpc connection");
                    session->start();
                }
                else {
                    this->l_->debug(sstrfmt("fails to accept a rpc connection due to error %d").fmt(err.value()));
                }

                this->start();
            });
        }
  • 前面说过每次client-listener通信都得走session,所以在acceptor_.async_accept里面如果没有err,那么就session->start(),启动新会话管理
  • 否则报错this->l_->debug(sstrfmt("fails to accept a rpc connection due to error %d").fmt(err.value()));
  • 因为listener得保持时刻监听端口,所以this->start();再次调用start函数,实现while(1){监听}

4.3 remove_session函数

  void remove_session(const ptr<rpc_session>& session) {
            auto_lock(session_lock_);
            for (std::vector<ptr<rpc_session>>::iterator it = active_sessions_.begin(); it != active_sessions_.end(); ++it) {
                if (*it == session) {
                    active_sessions_.erase(it);
                    break;
                }
            }
        }
  • 为了保证删除session的时候不被其他函数访问或者修改,首先给session_lock_auto_lock(实际上就是mutex互斥锁)。
  • 然后逐个遍历active_sessions_,从中找到session并删除

5. asio_rpc_session代码分析

5.1 初始化

    class rpc_session : public std::enable_shared_from_this<rpc_session> {
    public:
        template<typename SessionCloseCallback>
        rpc_session(asio::io_service& io, ptr<msg_handler>& handler, ptr<logger>& logger, SessionCloseCallback&& callback)
            : handler_(handler), socket_(io), log_data_(buffer::alloc(0)), header_(buffer::alloc(RPC_REQ_HEADER_SIZE)), l_(logger), callback_(std::move(callback)) {}

        __nocopy__(rpc_session)

    public:
        ~rpc_session() {
            if (socket_.is_open()) {
                socket_.close();
            }
        }

同前面初始化分析,没什么好说的

5.2 start函数分析

    public:
        void start() {
            ptr<rpc_session> self = this->shared_from_this(); // this is safe since we only expose ctor to cs_new
            asio::async_read(socket_, asio::buffer(header_->data(), RPC_REQ_HEADER_SIZE), [this, self](const asio::error_code& err, size_t) -> void {
                if (!err) {
                    header_->pos(RPC_REQ_HEADER_SIZE - 4);
                    int32 data_size = header_->get_int();
                    if (data_size < 0 || data_size > 0x1000000) {
                        l_->warn(lstrfmt("bad log data size in the header %d, stop this session to protect further corruption").fmt(data_size));
                        this->stop();
                        return;
                    }

                    if (data_size == 0) {
                        this->read_complete();
                        return;
                    }

                    this->log_data_ = buffer::alloc((size_t)data_size);
                    asio::async_read(this->socket_, asio::buffer(this->log_data_->data(), (size_t)data_size), std::bind(&rpc_session::read_log_data, self, std::placeholders::_1, std::placeholders::_2));
                }
                else {
                    l_->err(lstrfmt("failed to read rpc header from socket due to error %d").fmt(err.value()));
                    this->stop();
                }
            });
        }
  • 这里通过asio::async_read读取client发送过来的消息
  • 首先先读取消息头,根据消息头的data_size判断是否有数据
  • 如果data_size > 0 && data_size < 0x1000000 则继续读取后面真实的数据并存到log_data_

5.3 stop函数与socket分析

  void stop() {
            socket_.close();
            if (callback_) {
                callback_(this->shared_from_this());
            }
        }

        asio::ip::tcp::socket& socket() {
            return socket_;
        }

主要是stop关闭socket_的时候判断有没有回调函数,有就调用回调函数处理。

以上是public对外提供的接口,实际实现都放在了private的成员函数里面,下面我们分析private成员函数的具体实现。

5.4 read_log_data代码分析

log_data即为消息头后面紧跟着的真实数据。

 void read_log_data(const asio::error_code& err, size_t /* bytes_read */) {
            if (!err) {
                this->read_complete();
            }
            else {
                l_->err(lstrfmt("failed to read rpc log data from socket due to error %d").fmt(err.value()));
                this->stop();
            }
        }

可以看到如果没有err,就调用this->read_complete();来读取,所以读取数据的具体实现都放在了read_complete函数。

5.5 read_complete代码分析

void read_complete() {
            ptr<rpc_session> self = this->shared_from_this();
            try {
                header_->pos(0);
                msg_type t = (msg_type)header_->get_byte();
                int32 src = header_->get_int();
                int32 dst = header_->get_int();
                ulong term = header_->get_ulong();
                ulong last_term = header_->get_ulong();
                ulong last_idx = header_->get_ulong();
                ulong commit_idx = header_->get_ulong();
                ptr<req_msg> req(cs_new<req_msg>(term, t, src, dst, last_term, last_idx, commit_idx));
                if (header_->get_int() > 0 && log_data_) {
                    log_data_->pos(0);
                    while (log_data_->size() > log_data_->pos()) {
                        ulong term = log_data_->get_ulong();
                        log_val_type val_type = (log_val_type)log_data_->get_byte();
                        int32 val_size = log_data_->get_int();
                        bufptr buf(buffer::alloc((size_t)val_size));
                        log_data_->get(buf);
                        ptr<log_entry> entry(cs_new<log_entry>(term, std::move(buf), val_type));
                        req->log_entries().push_back(entry);
                    }
                }

                ptr<resp_msg> resp = handler_->process_req(*req);
                if (!resp) {
                    l_->err("no response is returned from raft message handler, potential system bug");
                    this->stop();
                }
                else {
                    bufptr resp_buf(buffer::alloc(RPC_RESP_HEADER_SIZE));
                    resp_buf->put((byte)resp->get_type());
                    resp_buf->put(resp->get_src());
                    resp_buf->put(resp->get_dst());
                    resp_buf->put(resp->get_term());
                    resp_buf->put(resp->get_next_idx());
                    resp_buf->put((byte)resp->get_accepted());
                    resp_buf->pos(0);
                    auto buffer = asio::buffer(resp_buf->data(), RPC_RESP_HEADER_SIZE);
                    asio::async_write(socket_, std::move(buffer), [this, self, buf = std::move(resp_buf)](asio::error_code err_code, size_t) -> void {
                        if (!err_code) {
                            this->header_->pos(0);
                            this->start();
                        }
                        else {
                            this->l_->err(lstrfmt("failed to send response to peer due to error %d").fmt(err_code.value()));
                            this->stop();
                        }
                    });
                }
            }
            catch (std::exception& ex) {
                l_->err(lstrfmt("failed to process request message due to error: %s").fmt(ex.what()));
                this->stop();
            }
        }
  • 首先先解析消息头,获取相应信息:
                header_->pos(0);
                msg_type t = (msg_type)header_->get_byte();
                int32 src = header_->get_int();
                int32 dst = header_->get_int();
                ulong term = header_->get_ulong();
                ulong last_term = header_->get_ulong();
                ulong last_idx = header_->get_ulong();
                ulong commit_idx = header_->get_ulong();
  • 然后用while循环取出所有数据,每一个数据都由(term,log_val_type,val_size,data)构成,
    把每一个数据都deep copyentry里面再存到req->log_entries
if (header_->get_int() > 0 && log_data_) {
                    log_data_->pos(0);
                    while (log_data_->size() > log_data_->pos()) {
                        ulong term = log_data_->get_ulong();
                        log_val_type val_type = (log_val_type)log_data_->get_byte();
                        int32 val_size = log_data_->get_int();
                        bufptr buf(buffer::alloc((size_t)val_size));
                        log_data_->get(buf);
                        ptr<log_entry> entry(cs_new<log_entry>(term, std::move(buf), val_type));
                        req->log_entries().push_back(entry);
                    }
                }
  • 生成对应的response:
    response的格式是(val_type,src,dst,term,next_idx,accepted)
    (实际上response,request都是派生自msg类,msg的构成是(val_type,src,dst,term)
                ptr<resp_msg> resp = handler_->process_req(*req);
                if (!resp) {
                    l_->err("no response is returned from raft message handler, potential system bug");
                    this->stop();
                }
                else {
                    bufptr resp_buf(buffer::alloc(RPC_RESP_HEADER_SIZE));
                    resp_buf->put((byte)resp->get_type());
                    resp_buf->put(resp->get_src());
                    resp_buf->put(resp->get_dst());
                    resp_buf->put(resp->get_term());
                    resp_buf->put(resp->get_next_idx());
                    resp_buf->put((byte)resp->get_accepted());
                    resp_buf->pos(0);
                    auto buffer = asio::buffer(resp_buf->data(), RPC_RESP_HEADER_SIZE);
                    asio::async_write(socket_, std::move(buffer), [this, self, buf = std::move(resp_buf)](asio::error_code err_code, size_t) -> void {
                        if (!err_code) {
                            this->header_->pos(0);
                            this->start();
                        }
                        else {
                            this->l_->err(lstrfmt("failed to send response to peer due to error %d").fmt(err_code.value()));
                            this->stop();
                        }
                    });
                }
            }

6. 总结

  • 1.异步操作中使用public enable_shared_from_this<A>,shared_ptr<A> self(this->shared_from_this());保证对象不失活。
  • 2.使用纯虚函数实现抽象类,强制子类必须提供相应虚函数实现。
  • 3.合理运用类里的public,private,将对外开放的,逻辑上必须具备的接口声明成public,具体实现声明成private。
  • 4.在rpc通信里面,client-listener的通信再抽象出来一个session管理,进一步解耦各模块。
posted @ 2024-10-31 17:23  TomGeller  阅读(4)  评论(0编辑  收藏  举报