tars客户端(一):一次rpc的调用过程

前言

用tars的客户端与tars服务端通信非常简单,因为框架层已经帮我们隐藏了寻址,协议封装,通信等细节,我们只需创建一个本地Servant代理,发起一个调用就可以。以官网的HelloServer为例:

//生成客户端代理
HelloPrx pPrx = Application::getCommunicator()->stringToProxy<HelloPrx>("Test.HelloServer.HelloObj");
//发起远程调用
string s = "hello word";
string r;
int ret = pPrx->testHello(s, r);

作为tars客户端的第一篇文章,会分两部分来初步了解tars客户端的实现:

  1. 先从顶层了解一下客户端的线程模型和客户端的各个类的职责
  2. 在熟悉了tars客户端的一些概念后,再结合代码看一下客户端发起rpc的过程

一.客户端的线程和各个代理概念

1.客户端线程

tars客户端rpc在一次调用中主要会有三个不同的线程参与:

  • 上图省略了很多细节,但是已经能够说明客户端的一次rpc调用请求的路线:主调线程发起调用,负责把请求序列化,封装成一个ReqMessage,push到消息队列中;网络线程负责处理所有的网络细节,包括链接的建立,请求发送,负载,容灾等,在收到响应后,再根据rpc调用方式决定是把响应返回给主调线程还是交由异步处理线程处理
  • 网络线程个数可配置,默认是1;每个网络线程有自己的一组异步处理线程,个数也可配,默认是3个

2.客户端主要类

  • Communicator:通信器,负责与服务端的通信。
  • ServantProxy:服务端Servant的本地代理,通过该类可以访问服务端的rpc接口。需要注意的是,这里的Servant是指tars寻址单位的Servant(即App.Server.Servant)
  • CommunicatorEpoll:基于epoll的客户端网络线程类
  • ObjectProxy:Servant在每个客户端线程的代理
  • EndPointManager:管理所有的AdapterProxy节点
  • AdapterProxy:服务端每个Adapter(对应着一个ip:port)的本地代理
  • Transceiver:负责具体的socket收发

类图如下:

 

每个ServantProxy可以有多个ObjectProxy,每个ObjectProxy又可以有多个AdapterProxy,它们的关系如下:

  • 黄色部分是ServantA在通信器中的各个代理;灰色部分是ServantB在通信器中的各个代理
  • 每个Servant在一个tars进程的通信器中只会有一个代理ServantProxy;Servant和ServantProxy是1:1的关系
  • ObjectProxy的个数和网络线程的个数有关,每个ServantProxy在每个网络线程中都有一个与之对应的ObjectProxy,例如这里的网络线程1和网络线程2都有1个ObjectProxyA;ServantProxy和ObjectProxy是1:多的关系
  • 每个ObjectProxy的EndpointManager里面又会有该Servant部署过的所有Adapter的代理,例如ServantA部署了ip1和ip2这2个节点,那么ObjectProxyA里面就会有AdapterPrxA_ip1和AdapterPrxA_ip2;ObjectProxy和AdapterProxy也是1:多的关系
  • 每个AdapterProxy都有一个属于自己的Transceiver,负责具体的网络收发工作

在与服务端的通信过程中,这些类都有自己负责的功能。下图的列子,服务端1个Servant部署两个节点(ip1,ip2),客户端通信器配了2个网络线程:

  1. ServantProxy负责发起rpc请求,序列化请求并准备好请求包,把请求包推到消息队列中
  2. 每个网络线程中都有一个ObjectProxy与每个ServantProxy对应,负责管理对应的Servant的协议解析器等
  3. ObjectProxy的EndPointManager根据负载规则和容灾规则选出合适的AdaptrProxy,把请求发给服务端
  4. AdapterProxy的Transceiver负责和服务端建立好TCP链接,进行socket收发
  5. 可以看到,在只有一个ServantProxy,两个网络线程,且每个Servant有两个活跃的Adapter的通信器中,总共会有4个TCP长链接与服务端保持链接状态

二.客户端一次rpc调用过程

1.调用流程

客户端发起rpc调用的过程,有2条主线,以同步调用方式为例子:

  1. 发起调用的线程,在发起请求后阻塞在条件变量上,直到网络线程通知它解除阻塞;
  2. 网络线程负责具有的请求收发工作

整个过程如下图:

  • 每个步骤都写上了对应的函数,可以根据这些步骤在对应的代码里看实现细节
  • 网络线程的io模型和主要逻辑和我们之前讲到的服务端网络线程的很像tars服务端(二):网络io模型和线程模型,都是基于epoll的io多路复用 + 非阻塞socket;另外,客户端的网络线程将epoll的io事件分为两类:(1)E_C_NOTIFY:主调线程通知网络线程(2)E_C_NET:socket读写事件
  • 整个rpc调用过程中还有很多细节没体现在上图:主调线程和网络线程的交互细节,具体的收发包工作,Adapter节点管理,容载,负载均衡等,会在之后的文章中单独讲到

2.网络线程的生成和启动

在发起rpc调用之前,需要生成客户端代理:HelloPrx pPrx = Application::getCommunicator()->stringToProxy<HelloPrx>

stringToProxy的代码如下:

    template<class T> T stringToProxy(const string& objectName,const string& setName="")                                                                                                       
    {                                                                                                                                                                                          
        T prx = NULL;                                                                                                                                                                          
                                                                                                                                                                                               
        stringToProxy<T>(objectName, prx,setName);                                                                                                                                             
                                                                                                                                                                                               
        return prx;                                                                                                                                                                            
    }                                                                                                                                                                                          
                                                                                                                                                                                               
    /**                                                                                                                                                                                        
     * 生成代理                                                                                                                                                                                
     * @param T                                                                                                                                                                                
     * @param objectName                                                                                                                                                                       
     * @param setName 指定set调用的setid                                                                                                                                                       
     * @param proxy                                                                                                                                                                            
     */                                                                                                                                                                                        
    template<class T> void stringToProxy(const string& objectName, T& proxy,const string& setName="")                                                                                          
    {                                                                                                                                                                                          
        ServantProxy * pServantProxy = getServantProxy(objectName,setName);                                                                                                                    
        proxy = (typename T::element_type*)(pServantProxy);                                                                                                                                    
    }                                      

stringToProxy最终调用的是通信器的getServantProxy

ServantProxy * Communicator::getServantProxy(const string& objectName,const string& setName)                                                                                                   
{                                                                                                                                                                                              
    Communicator::initialize();                                                                                                                                                                
                                                                                                                                                                                               
    return _servantProxyFactory->getServantProxy(objectName,setName);                                                                                                                          
}

如果是第一次调用,则会在Communicator::initialize中创建并启动网络线程CommunicatorEpoll:

void Communicator::initialize()                                                                                                                                                                
{                                                                                                                                                                                              
    TC_LockT<TC_ThreadRecMutex> lock(*this);                                                                                                                                                   
                                                                                                                                                                                               
    if (_initialized)                                                                                                                                                                          
        return;                                                                                                                                                                                
                                                                                                                                                                                               
    _initialized = true;                                                                                                                                                                       
                                                                                                                                                                                               
    _servantProxyFactory = new ServantProxyFactory(this);                                                                                                                                      
                                                                                                                                                                                               
                                                                                                                                                                                               
    //客户端网络线程                                                                                                                                                                           
    _clientThreadNum = TC_Common::strto<size_t>(getProperty("netthread","1"));                                                                                                                 
                                                                                                                                                                                               
    if(0 == _clientThreadNum)                                                                                                                                                                  
    {                                                                                                                                                                                          
        _clientThreadNum = 1;                                                                                                                                                                  
    }                                                                                                                                                                                          
    else if(MAX_CLIENT_THREAD_NUM < _clientThreadNum)                                                                                                                                          
    {                                                                                                                                                                                          
        _clientThreadNum = MAX_CLIENT_THREAD_NUM;                                                                                                                                              
    }                                    

    //stat总是有对象, 保证getStat返回的对象总是有效                                                                                                                                            
    _statReport = new StatReport(_clientThreadNum);                                                                                                                                            

    for(size_t i = 0; i < _clientThreadNum; ++i)
    {
        _communicatorEpoll[i] = new CommunicatorEpoll(this, i);
        _communicatorEpoll[i]->start();
    }

    .................
}

可以看到网络线程的个数可以在客户端的netthread中配置,默认是1个。至此,网络线程启动。

然后在网络线程类CommunicatorEpoll的构造函数中还创建并启动了异步处理线程:

    //异步线程数                                                                                                               
    _asyncThreadNum = TC_Common::strto<size_t>(pCommunicator->getProperty("asyncthread", "3"));                                
                                                                                                                               
    if(_asyncThreadNum == 0)                                                                                                   
    {                                                                                                                          
        _asyncThreadNum = 3;                                                                                                   
    }                                                                                                                          
                                                                                                                               
    if(_asyncThreadNum > MAX_CLIENT_ASYNCTHREAD_NUM)                                                                           
    {                                                                                                                          
        _asyncThreadNum = MAX_CLIENT_ASYNCTHREAD_NUM;                                                                          
    }                              

    ........
    

    //创建异步线程
    for(size_t i = 0; i < _asyncThreadNum; ++i)
    {
        _asyncThread[i] = new AsyncProcThread(iAsyncQueueCap);
        _asyncThread[i]->start();
    }

异步线程个数在客户端的asyncthread中配置。而且是每个网络线程分别有一组异步处理线程。所以,如果一个通信器被配置成2个网络线程,3个异步线程,那么总的异步线程数为3*2=6个。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章