在glusterfs中,gluster与glusterd通信请求对卷的操作、集群的操作、状态的查看等;glusterd与glusterfsd通信完成对卷的操作,集群的操作,状态的查看;glusterfs与glusterfsd通信完成文件的存储。所有这些通信都是通过内部的RPC模块来完成的。
有关RPC的相关概念、协议等这里不展开描述,有兴趣的可以看看这两篇文章(1, 2)。
=========================================
从代码的组织来看,RPC的服务端逻辑上可分为四层,server-app、rpc-server、rpc-transport、protocol,每一层都提供相应的接口供上一层调用,同时,上一层会提供回调函数供下一层来调用;同样,RPC的客户端逻辑上也可分为四层,cli-app、rpc-cli、rpc-transport、protocol。目前,protocol提供了tcp(socket)和rdma两种方式,且都以动态库的方式提供,rpc-transport会根据配置加载不同的动态库。我们以gluster与glusterd的通信并选用tcp的方式为例来看看RPC相关流程。
1. 服务端的初始化
关键流程如图所示:
需要注意的是:rpc_transport_load时会根据协议的类型加载(使用dlopen)不同的动态库,调用socket_listen时将fd与回调函数添加事件驱动器中。当有读写事件时,事件驱动器回调socket_server_event_handler函数(用于服务端的accept)或者socket_event_handler函数(用于一般的请求),然后依次回调rpc_transport_notify、rpcsvc_notify处理RPC请求。
2. 客户端的初始化
关键流程:
socket_connect函数会将fd以及回调处理函数注册到事件驱动器中。
3. 一次完整的RPC流程
(1) 客户端发送RPC请求
关键的流程与数据结构:
客户端通过调用rpc_clnt_submit函数发送RPC请求,该函数又会一层层调用,最终在socket_submit_request中通过writev将请求发送出去。在调用rpc_clnt_submit时会准备好RPC所需要的相关数据,例如程序号,程序版本号,过程号,参数信息等等,然后逐层按照接口组织好相关的数据。
例如: 执行 gluster volume info命令,其内部关键代码:
int32_t gf_clie_1_get_volume(call_frame_t * frame, xlator_t * this)
{
...
ret = cli_cmd_submit(&req,
frame,
cli_rpc_prog, //包含程序名,程序号,程序版本号等信息
GLUSTER_CLI_GET_VOLUME, //过程号
NULL,
this,
gf_cli3_1_get_volume_cbk, //结果处理回调函数
(xdrproc_t)xdr_gf_cli_req);
..
}
int cli_cmd_submit(void *req, call_frame_t * frame,
rpc_clnt_prog_t * prog, int procnum,
struct iobref * iobref,
xlator_t * this, fop_cbk_fn_t cbkfn,
xdrproc_t xdrproc)
{
...
ret = cli_submit_request(req, frame, prog, procnum, NULL, this,
cbkfn, xdrproc);
...
}
int cli_submit_request(void * req, call_frame_t * frame,
rpc_clnt_prog_t * prog, int procnum,
struct iobref * iobref,
xlator_t * this, fop_cbk_fn_t cbkfn,
xdrproc_t xdrproc)
{
...
ret = rpc_clnt_submit(global_rpc, prog, procnum, cbkfn,
&iov, count, NULL, 0, iobref, frame,
NULL, 0, NULL, 0, NULL)
}
int rpc_clnt_submit(struct rpc_clnt * rpc,
rpc_clnt_prog_t * prog, int procnum,
fop_cbk_fn_t cbkfn,
struct iovec * proghdr, int proghdrcount,
struct iovec * progpayload, int progpayloadcount,
struct iobref * iobref, void * frame,
struct iovec * rsphdr, int rsphdr_count,
struct iovec * rsp_payload,int rsp_payload_count,
struct iobref * rsp_iobref)
{
struct iobuf * request_iob = NULL;
rpc_transport_req_t req;
...
request_iob = rpc_clnt_record(rpc, frame, prog, procnum, proglen,
&rpchdr, callid);
req.msg.rpchdr = &rpchdr;
req.msg.rpchdrcount = 1;
req.msg.proghdr = proghdr;
req.msg.proghdrcount = proghdrcount;
req.msg.progpayload = progpayload;
req.msg.progpayloadcount = progpayloadcount;
req.msg.iobref = iobref;
...
ret = rpc_transport_submit_request(rpc->conn.trans, &req);
...
}
int32_t rpc_transport_submit_request(rpc_transport_t * this,
rpc_transport_req_t * req)
{
ret = this->ops->submit_request(this, req);
}
int32_t socket_submit_request(rpc_transport_t * this,
rpc_transport_req_t * req)
{
struct ioq * entry = NULL;
entry = __socket_ioq_new(this, &req->msg);
ret = __socket_ioq_churn_entry(this, entry);
...
}
int __socket_ioq_churn_entry(rpc_transport_t *this,
struct ioq * entry)
{
ret = __socket_writev(this, entry->pending_vector,
entry->pending_count,
&entry->pending_vector,
&entry->pending_count);
...
}
int __socket_writev(rpc_transport_t * this,
struct iovec * vector, int count,
struct iovec **pending_vector, int *pendint_count)
{
ret = __socket_rwv(this, vector, count, pending_vector,
pending_count, NULL, 1);
...
}
int __socket_rwv(rpc_transport_t *this, struct iovec *vector,
int count, struct iovec **pending_vector,
int * pending_count, size_t * bytes, int write)
{
int opcount = 0;
struct iovec * opvector = NULL;
opvector = vector;
opcount = count;
while(opcount)
{
if(write)
{
ret = wrtiev(sock, opvector, opcount);
}
...
}
...
}
(2) 服务端处理RPC请求
服务端收到请求后,从socket_event_handler回调到rpc_transport_notify,再回调到rpcsvc_notify,最终调用rpcsvc_handle_rpc_call函数。在这个函数中,解析客户端RPC请求中包含的程序号,过程号以及相关参数等,然后根据这些程序号,过程号找到对应的处理函数。而这些处理函数就是先前通过rpcsvc_program_register函数注册的。对应的处理函数处理完成后调用相关函数答复客户端。
注: actor并不是一个真正的函数,仅标识不同RPC请求的处理函数.
(3) 客户端处理RPC的回复
客户端在发送请求时,会将请求的相关信息缓存下来,当收到服务器的回应后,再根据程序号、过程号找到对应的请求信息,然后调用相应的回调函数对请求结果进行处理。
========================================
总结:文本仅仅讲述了RPC的大概运行流程,其中还有很多细节以及相关点都未涉及到,例如状态机、锁机制、frame概念、xdr等等,后续需要进一步展开学习研究。
来源:oschina
链接:https://my.oschina.net/u/184909/blog/213683