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個。

 

 

 

 

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