glusterfs通信之rpc

南笙酒味 提交于 2020-03-07 20:45:28

在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等等,后续需要进一步展开学习研究。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!