tars开源框架地址:https://github.com/Tencent/Tars
系列文章:
简介:Tars是腾讯从2008年到今天一直在使用的后台逻辑层的统一应用框架TAF(Total Application Framework),目前支持C++,Java,PHP,Nodejs语言。该框架为用户提供了涉及到开发、运维、以及测试的一整套解决方案,帮助一个产品或者服务快速开发、部署、测试、上线。 它集可扩展协议编解码、高性能RPC通信框架、名字路由与发现、发布监控、日志统计、配置管理等于一体,通过它可以快速用微服务的方式构建自己的稳定可靠的分布式应用,并实现完整有效的服务治理。目前该框架在腾讯内部,各大核心业务都在使用,颇受欢迎,基于该框架部署运行的服务节点规模达到上万个。
2021-02-15 春节假期,翻开博客看到这篇文章还有接近2千个阅读量,虽然已经离开工作6年的鹅厂了,但当时写这个文章一方面为了自己巩固,一方面希望好的开源框架能够帮助更多的人提高开发效率和品质,不只在鹅厂可以使用,之前文章的图用viso画的,自己画图能力确实太弱,这次准备利用春节假期重新用processon在线画图工具重新理下,希望能表达得更清晰些
一、客户端同步调用远程服务部分:
使用过tars的人应该都能感受到RPC功能的方便性,tars调用远程服务的方法如下:
m_pRoleServantPrx = Application::getCommunicator()->stringToProxy<RoleServantPrx>(GAMECONFIG->sRoleServerObj);
这样一句代码就可以实现获取远程服务RoleServer的rpc接口了,接着接口调用远程服务RoleServer实现定义好的接口
例如getRoleInfo();m_pRoleServantPrx->getRoleInfo(iUin, vData); 两行代码完成一个rpc调用。是我用过的最方便的rpc框架没有之一了
具体框架源码做了什么事情,通过下图然后后续展开来分析
上图已经用数字标注了步骤
对应上图的第一步:图上看到做为客户端进行rpc调用时候使用了:Application::getCommunicator() 获取通讯器:获取到了之后调用stringToProxy模板函数传入参数为obj名字例如上图的:
RoleServerObj(在我们游戏中RoleServer它是一个负责维护角色信息的微服务,格式:servant=HERMAN.RoleServer.RoleServantObj),stringToProxy会根据传参obj的名字调用ServantProxyFactory::getServantProxy获得远程服务对应的servant代理类:(如果_servantProxy没有该obj对应的servant则新new一个并且放入到map<string, ServantPrx> _servantProxy;)。这里要注意可以配置多个客户端网络线程(默认一个),所以new ObjectProxy是一个指针数组ObjectProxy ** ppObjectProxy = new ObjectProxy * [_comm->getClientThreadNum()];new好ObjectProxy然后调用ppObjectProxy[i] = _comm->getCommunicatorEpoll(i)->getObjectProxy(name, setName)根据_communicatorEpoll和sObjectProxyName生成ObjectProxy并且赋值给ppObjectProxy[i] ;注意在框架启动时候Communicator::initialize初始化的时候就已经根据配置的客户端网络线程数生成对应个数的CommunicatorEpoll,所以getCommunicatorEpoll(i)是从数组里面去把CommunicatorEpoll取出来,并且initialize会启动CommunicatorEpoll的线程函数。
接下来构造好ObjectProxy之后,构造new ServantProxy需要传入ObjectProxy为参数,每个objectname在每个客户端网络线程中有唯一一个objectproxy。所以前面stringToProxy不出意外必然会获得一个obj对应的代理类ServantProxy,然后根据传入函数模板的T强制转换下:proxy = (typename T::element_type*)(pServantProxy);例如我这里就是转成:RoleServantPrx,RoleServantPrx是继承于ServantProxy类的(由rpc定义的接口文件自动生成例如.h文件,例如GateServant.h)。
到这里有了调用stringToProxy就干完活了,总结下来就是获取一个ServantProxy,这个对象就是远程要调用的服务的代理类了,然后如上图,就是调用远程服务器定义好的rpc接口,例如上图就是getRoleInfo接口,需要入参uin,出参vData返回根据账号获取到的角色信息了。
有了RoleServantPrx之后,是怎么实现远程rpc调用的呢,为啥RoleServantPrx类调用getRoleInfo函数就可以获取到远程服务返回的信息,这里先看下tars的远程服务接口定义:
例如:GateServant.tars(.tars是tars框架统一的接口定义格式文件) 这是我这边一个网关服务(负责玩家长连接的)的接口定义文件,定义了doRequest接口,调用框架自带的脚本:tars2cpp(通常安装路径在:/home/herman/Tars-master/framework/tarscpp/tools/tars2cpp)会自动生成对应的.h头文件
例如这里就是GateServant.h,GateServant到底生成了啥代码,需要打开看下:
GateServant.tars文件:
module HERMAN
{
interface GateServant
{
int test();
int doRequest(string sRequest, out string sResponse);
};
};
生成的.h文件代码片段
class GateServantProxy : public tars::ServantProxy
{
tars::Int32 doRequest(const std::string & sRequest,std::string &sResponse,const map<string, string> &context = TARS_CONTEXT(),map<string, string> * pResponseContext = NULL
{
tars::TarsOutputStream<tars::BufferWriterVector> _os;
_os.write(sRequest, 1);
_os.write(sResponse, 2);
std::map<string, string> _mStatus;
shared_ptr<tars::ResponsePacket> rep = tars_invoke(tars::TARSNORMAL,"doRequest", _os, context, _mStatus);
if(pResponseContext)
{
pResponseContext->swap(rep->context);
}
}
}
重点看下生成的tars_invoke函数调用,传入第一个参数tars::TARSNORMAL(还有TARSONEWAY代表单向调用不接收返回值等),第二个参数接口名,第三个os,就是入参和出参,
第四个参数上下文,第五个参数状态,注意这里tars_invoke是GateServantProxy的基类ServantProxy的成员函数:tars_invoke的实现如下,这里新版本ResponsePacket返回包作为引用出参,老版本应该是直接返回
void ServantProxy::tars_invoke(char cPacketType,
const string& sFuncName,
const vector<char>& buf,
const map<string, string>& context,
const map<string, string>& status,
ResponsePacket& rsp)
{
ReqMessage * msg = new ReqMessage();
msg->init(ReqMessage::SYNC_CALL,NULL,sFuncName); //SYNC_CALL代表同步调用,tars