cornerstone中rpc通信源码解析
1.概述
asio_service是cornerstone中实现各种服务的工具,rpc通信属于其中的一部分,rpc通信主要由三个部分组成:
asio_rpc_client
,asio_rpc_listener
,asio_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_write
,async_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 copy到entry
里面再存到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管理,进一步解耦各模块。