微服务开源框架TARS的RPC源码解析 之 初识TARS C++客户端

本文是《微服务开源框架TARS的RPC源码解析》系列的上篇,主要介绍TARS C++客户端的结构与工作原理。内容包括:1. 客户端核心类Communicator、ServantProxy与ServantProxyFactory、CommunicatorEpoll的角色和职责;2. 初始化过程中的类关系和调用流程;3. 同步调用的实现细节,涉及网络线程发送请求和接收响应;4. 对异步调用的简要说明。通过源码解析,帮助读者理解TARS客户端的RPC调用机制。
摘要由CSDN通过智能技术生成

banner

作者:Cony
导语:微服务开源框架TARS的RPC调用包含客户端与服务端,《微服务开源框架TARS的RPC源码解析》系列文章将从初识客户端、客户端的同步及异步调用、初识服务端、服务端的工作流程四部分,以C++语言为载体,深入浅出地带你了解TARS RPC调用的原理。

什么是TARS

TARS是腾讯使用十年的微服务开发框架,目前支持C++、Java、PHP、Node.js、Go语言。该开源项目为用户提供了涉及到开发、运维、以及测试的一整套微服务平台PaaS解决方案,帮助一个产品或者服务快速开发、部署、测试、上线。目前该框架应用在腾讯各大核心业务,基于该框架部署运行的服务节点规模达到数十万。
TARS的通信模型中包含客户端和服务端。客户端服务端之间主要是利用RPC进行通信。本系列文章分上下两篇,对RPC调用部分进行源码解析。本文是上篇,我们将以C++语言为载体,带大家了解一下TARS的客户端。

初识客户端

TARS的客户端最重要的类是Communicator,一个客户端只能声明出一个Communicator类实例,用户可以通过CommunicatorPtr& Application::getCommunicator()获取线程安全的Communicator类单例。Communicator类聚合了两个比较重要的类,一个是CommunicatorEpoll,负责网络线程的建立与通过ObjectProxyFactory生成ObjectProxy;另一个是ServantProxyFactory,生成不同的RPC服务句柄,即ServantProxy,用户通过ServantProxy调用RPC服务。下面简单介绍几个类的作用。

Communicator

一个Communicator实例就是一个客户端,负责与服务端建立连接,生成RPC服务句柄,可以通过CommunicatorPtr& Application::getCommunicator()获取Communicator实例,用户最后不要自己声明定义新的Communicator实例。

ServantProxy与ServantProxyFactory

ServantProxy就是一个服务代理,ServantProxy可以通过ServantProxyFactory工厂类生成,用户往往通过Communicator的template void stringToProxy()接口间接调用ServantProxyFactory的ServantPrx::element_type* getServantProxy()接口以获取服务代理,通过服务代理ServantProxy,用户就可以进行RPC调用了。ServantProxy内含多个服务实体ObjectProxy(详见下文第4小点),能够帮助用户在同一个服务代理内进行负载均衡。

CommunicatorEpoll

CommunicatorEpoll类代表客户端的网络模块,内含TC_Epoller作为IO复用,能够同时处理不同主调线程(caller线程)的多个请求。CommunicatorEpoll内含服务实体工厂类ObjectProxyFactory(详见下文第4小点),意味着在同一网络线程中,能够产生不同服务的实体,能够完成不同的RPC服务调用。CommunicatorEpoll还聚合了异步调用处理线程AsyncProcThread,负责接收到异步的响应包之后,将响应包交给该线程处理。

ObjectProxy与ObjectProxyFactory

ObjectProxy类是一个服务实体,注意与ServantProxy类是一个服务代理相区别,前者表示一个网络线程上的某个服务实体A,后者表示对所有网络线程上的某服务实体A的总代理,更详细的介绍可见下文。ObjectProxy通过ObjectProxyFactory生成,而ObjectProxyFactory类的实例是CommunicatorEpoll的成员变量,意味着一个网络线程CommunicatorEpoll能够产生各种各样的服务实体ObjectProxy,发起不同的RPC服务。ObjectProxy通过AdapterProxy来管理对服务端的连接。
好了,介绍完所有的类之后,先通过类图理一理他们之间的关系,这个类图在之后的文章中将会再次出现。

图(1-1)客户端类图

TARS的客户端最重要的类是Communicator,一个客户端只能声明出一个Communicator类实例,用户可以通过CommunicatorPtr& Application::getCommunicator()获取线程安全的Communicator类单例。Communicator类聚合了两个比较重要的类,一个是CommunicatorEpoll,负责网络线程的建立与通过ObjectProxyFactory生成ObjectProxy;另一个是ServantProxyFactory,生成不同的RPC服务句柄,即ServantProxy,用户通过ServantProxy调用RPC服务。
根据用户配置,Communicator拥有n个网络线程,即n个CommunicatorEpoll。每个CommunicatorEpoll拥有一个ObjectProxyFactory类,每个ObjectProxyFactory可以生成一系列的不同服务的实体对象ObjectProxy,因此,假如Communicator拥有两个CommunicatorEpoll,并有foo与bar这两类不同的服务实体对象,那么如下图(1-2)所示,每个CommunicatorEpoll可以通过 ObjectProxyFactory创建两类ObjectProxy,这是TARS客户端的第一层负载均衡,每个线程都可以分担所有服务的RPC请求,因此,一个服务的阻塞可能会影响其他服务,因为网络线程是多个服务实体ObjectProxy所共享的。

图(1-2)Communicator中的CommunicatorEpoll与ObjectProxy

Communicator类下另一个比较重要的ServantProxyFactory类的作用是依据实际服务端的信息(如服务器的socket标志)与Communicator中客户端的信息(如网络线程数)而生成ServantProxy句柄,通过句柄调用RPC服务。举个例子,如下图(1-3)所示,Communicator实例通过ServantProxyFactory成员变量的getServantProxy()接口在构造fooServantProxy句柄的时候,会获取Communicator实例下的所有CommunicatorEpoll(即CommunicatorEpoll-1与CommunicatorEpoll-2)中的fooObjectProxy(即fooObjectProxy-1与fooObjectProxy-2),并作为构造fooServantProxy的参数。Communicator通过ServantProxyFactory能够获取foo与bar这两类ServantProxy,ServantProxy与相应的ObjectProxy存在相应的聚合关系:
图(1-3)Communicator中的ServantProxyFactory与ObjectProxy

另外,每个ObjectProxy都拥有一个EndpointManager,例如,fooObjectProxy 的EndpointManager管理fooObjectProxy 下面的所有fooAdapterProxy,每个AdapterProxy连接到一个提供相应foo服务的服务端物理机socket上。通过EndpointManager还可以以不同的负载均衡方式获取连接AdapterProxy。假如foo服务有两台物理机,bar服务有一台物理机,那么ObjectProxy,EndpointManager与AdapterProxy关系如下图(1-4)所示。上面提到,不同的网络线程CommunicatorEpoll均可以发起同一RPC请求,对于同一RPC服务,选取不同的ObjectProxy(或可认为选取不同的网络线程CommunicatorEpoll)是第一层的负载均衡,而对于同一个被选中的ObjectProxy,通过EndpointManager选择不同的socket连接AdapterProxy(假如ObjectProxy有大于1个的AdapterProxy,如图(1-4)的fooObjectProxy)是第二层的负载均衡。
图(1-4)ObjectProxy与AdapterProxy的关系

在客户端进行初始化时,必须建立上面介绍的关系,因此相应的类图如图(1-5)所示,通过类图可以看出各类的关系,以及初始化需要用到的函数。
图(1-5)客户端初始化后建立的类图

初始化代码

现在,通过代码跟踪来看看,在客户端初始化过程中,各个类是如何被初始化出来并建立上述的架构关系的。在简述之前,可以先看看函数的调用流程图,若看不清晰,可以将图片保存下来,用看图软件放大查看,强烈建议结合文章的代码解析以TARS源码一起查看,文章后面的所有代码流程图均如此。
接下来,将会按照函数调用流程图来一步一步分析客户端代理是如何被初始化出来的:

图(1-6) 初始化函数调用过程图

1. 执行stringToProxy

在客户端程序中,一开始会执行下面的代码进行整个客户端代理的初始化:

Communicator comm;
HelloPrx prx;
comm.stringToProxy("TestApp.HelloServer.HelloObj@tcp -h 1.1.1.1 -p 20001" , prx);

先声明一个Communicator变量comm(其实不建议这么做)以及一个ServantProxy类的指针变量prx,在此处,服务为Hello,因此声明一个HelloPrx prx。注意一个客户端只能拥有一个Communicator。为了能够获得RPC的服务句柄,我们调用Communicator::stringToProxy(),并传入服务端的信息与prx变量,函数返回后,prx就是RPC服务的句柄。
进入Communicator::stringToProxy()函数中,我们通过Communicator::getServantProxy()来依据objectName与setName获取服务代理ServantProxy:

/**
* 生成代理
* @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);
}

2.执行Communicator的初始化函数

进入Communicator::getServantProxy(),首先会执行Communicator::initialize()来初始化Communicator,需要注意一点,Communicator:: initialize()只会被执行一次,下一次执行Communicator::getServantProxy()将不会再次执行Communicator:: initialize()函数:

void Communicator::initialize()
{
   
    TC_LockT<TC_ThreadRecMutex> lock(*this);
    if (_initialized) //已经被初始化则直接返回
        return;
    ......

进入Communicator::initialize()函数中,在这里,将会new出上文介绍的与Communicator密切相关的类ServantProxyFactory与n个CommunicatorEpoll,n为客户端的网络线程数,最小为1,最大为MAX_CLIENT_THREAD_NUM:

void Communicator::initialize()
{
   
    ......
    _servantProxyFactory = new ServantProxyFactory(this);
    ......
    for(size_t i = 0; i < _clientThreadNum; ++i)
    {
   
        _communicatorEpoll[i] = new CommunicatorEpoll(this, i);
        _communicatorEpoll[i]->start(); //启动网络线程
    }
    ......

在CommunicatorEpoll的构造函数中,ObjectProxyFactory被创建出来,这是构造图(1-2)关系的前提。除此之外,还可以看到获取相应配置,创建并启动若干个异步回调后的处理线程。创建完成后,调用CommunicatorEpoll::start()启动网络线程。至此,Communicator::initialize()顺利执行。通过下图回顾上面的过程:

图(1-7)执行Communicator的初始化函数流程

3.尝试获取ServantProxy

代码回到Communicator::getServantProxy()中 Communicator::getServantProxy()会执行ServantProxyFactory::getServantProxy()并返回相应的服务代理:

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

进入ServantProxyFactory::getServantProxy(),首先会加锁,从map<string, ServantPrx> _servantProxy中查找目标,若查找成功直接返回。若查找失败,TARS需要构造出相应的ServantProxy,ServantProxy的构造需要如图(1-3)所示的相对应的ObjectProxy作为构造函数的参数,由此可见,在ServantProxyFactory::getServantProxy()中有如下获取ObjectProxy指针数组的代码:

ObjectProxy ** ppObjectProxy = new ObjectProxy * [_comm->getClientThreadNum()];
assert(ppObjectProxy != NULL);
for(size_t i = 0; i < _comm->getClientThreadNum(); ++i)
{
   
    ppObjectProxy[i] = _comm->getCommunicatorEpoll(i)->getObjectProxy(name, setName);
}

4.获取ObjectProxy

代码来到ObjectProxyFactory::getObjectProxy(),同样,会首先加锁,再从map<string,ObjectProxy*> _objectProxys中查找是否已经拥有目标ObjectProxy,若查找成功直接返回。若查找失败,需要新建一个新的ObjectProxy,通过类图可知,ObjectProxy需要一个CommunicatorEpoll对象进行初始化,由此关联管理自己的CommunicatorEpoll,CommunicatorEpoll之后便可以通过getObjectProxy()接口获取属于自己的ObjectProxy。详细过程可见下图:

图(1-8)获取ObjectProxy流程

5.建立ObjectProxy与AdapterProxy的关系

新建ObjectProxy的过程同样非常值得关注,在ObjectProxy::ObjectProxy()中,关键代码是:

_endpointManger.reset(new EndpointManager(this, _communicatorEpoll->getCommunicator(
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值