总结Tars中一些重要的原理与使用方式,内容可能会有部分内容与官方docs重合,但绝对不会是官方docs的搬运。
本文适合已经可以自行安装好Tars,并且使用过一段时间Tars,对其有一些基本感知的人阅读。当然,同时也欢迎Tars新鸟、老鸟来交流。
Tars中一些重要的概念
Tars核心点有四个:
- 一套服务器框架(特点:高稳定性、高可用性、高性能);
- 十分方便的内建RPC机制(特点:开发使用简单方便);
- 丰富的公用库(特点:封装了大部分常用方法,易于使用);
- 服务治理平台(功能:服务部署、发布、配置、监控、调度管理、容灾);
Tars中的客户端:
这是一个比较容易混淆的概念,Tars是一个服务器框架,这里的客户端只是对调用接口者一种统称:
- Tars里的客户端,做个比喻:有两个Tars Server – ServerA与ServerB,ServerA需要调用ServerB提供的接口获取信息,那么此时ServerA看做成客户端,ServerB看做成服务端;
在Tars内部,对于客户端,提供了四种调用服务端方式:
- 同步阻塞,官方有样例,代码结构最简单;
- 异步非阻塞,官方有样例,需要自己继承CallBack实现;
- Future/Promise,官方无样例,异步代码同步化;
- Coroutine,官方无样例,在配置里面[OpenCoroutine]默认是关闭的;
一句经验之谈:Tars的客户端一般是属于被服务端“利用”的状态,即在服务端的代码中利用上述四种方式调用其他Tars服务端的接口(隐藏了客户端的概念)。
所以,仅仅只是使用Tars,我们不必纠结客户端的概念,只需要知道它大概是一个什么角色即可,若是想要深入源码去剖析、学习Tars原理就必须得要区分清楚。
需要注意是:Tars客户端支持多线程,可在[运维管理]->[模版管理]->[编辑tars.default]中的<client>标签下面加入netthread = n修改客户端线程的数量(默认值是1)。
每个客户端线程下面会持有m个异步处理线程,异步处理线程主要是处理上述接口调用方式后三种的调用结果,其值可以在[服务列表]->[编辑]->[异步线程数]输入框里面修改线程数量。
为什么需要知道这些设置?因为根据这些设置,再结合机器资源与自己要实现的业务属性,可以定制化自己需要的线程数,达到资源利用最优!
Tars中的服务端:
在Tars中,具体的业务服务都从Servant继承实现,一个Tars节点可以拥有多个不同的Servant,这些Servant可以根据需要自主的选择Tars/非Tars协议,Tcp/Udp协议。
推荐的做法是:Tars内部之间的服务提供可以选择Tars协议 + Tcp(在同一IDC基本网络耗时在1-2ms);对外非Tars提供服务选择非Tars协议 + Tcp/Udp。
需要注意是:Tars的Udp是原生的,若想实现Udp的有序和可靠,且不修改Tars源码,可以选择嵌入KCP。
Tars服务端也支持多线程,可在[管理Servant]->[编辑]->[线程数]输入框里面修改线程数量。
Tars客户端使用
抽象一个场景:Tars应用Lan,其中一个服务名字叫TestServer,该服务提供业务接口的Servant叫TestServant,提供Hello()这个接口,这个服务有n个节点。
现在在同样的应用Lan里面,有一个Tars服务作为客户端想调用Hello()接口:
TestServantPrx testPrx = Application::getCommunicator()->stringToProxy<TestServantPrx>("Lan.TestServer.TestServant"); //这个方法会自动找到有效节点进行轮询调用 testPrx->Hello(); //可以获得刚才调用节点的IP、PORT相关信息 //const TC_Endpoint ep = testPrx->tars_invoke_endpoint();
这次调用默认是按轮询的,tars也提供了hash方式的调用,保证相同key,会调用相同的active节点(这里隐含的重点是会自动屏蔽掉inactive的节点):
TestServantPrx testPrx = Application::getCommunicator()->stringToProxy<TestServantPrx>("Lan.TestServer.TestServant"); testPrx->tars_hash(0)->Hello(); testPrx->tars_consistent_hash(0)->Hello();
也可以指定节点调用,前提你需要知道它的协议,IP,PORT:
TestServantPrx testPrx = Application::getCommunicator()->stringToProxy<TestServantPrx>("Lan.TestServer.TestServant@tcp -h 10.120.129.226 -p 20001");
获得对应TestServer所有节点的IP列表:
TestServantPrx testPrx = Application::getCommunicator()->stringToProxy<TestServantPrx>("Lan.TestServer.TestServant"); vector<EndpointInfo> actives; //有效节点列表 vector<EndpointInfo> inactives; //失效节点列表 testPrx->tars_endpointsAll(actives, inactives); vector<EndpointInfo>::const_iterator it = actives.begin(); for ( ; it != actives.end(); it++) { const EndpointInfo &ep = *it; //ep里面包含了协议、IP、PORT等等信息 }
Tars服务端一些方法
场景和客户端相同,我们想获取当前TestServant节点的协议、IP、PORT等信息:
//需要在TestServant的子类里调用 TC_EpollServer *e = getHandle()->getEpollServer(); const TC_Endpoint ep = e->getBindAdapter(ServerConfig::Application + "." + ServerConfig::ServerName + ".TestServantAdapter")->getEndpoint(); //ep里面包含了协议、IP、PORT等等信息
获得当前SET相关信息
if (tars::ClientConfig::SetOpen && !tars::ClientConfig::SetDivision.empty()) { vector<string> set = TC_Common::sepstr<string>(tars::ClientConfig::SetDivision, "."); //set[0]-Set名 set[1]-Set区域 set[2]-Set组 }
怎么看待Tars服务端协议接口?
Tars所有的接口都是socket在TCP/UDP应用层的封装,不论你用的什么语言,Java,C++,PHP……只要有socket存在的地方Tars接口就可以被调用。所以任何系统任何语言只要通过socket按照特定的协议格式都可以与Tars服务进行连接。具体原理与步骤可以参考官方的文档:TUP。
现在Tars加入了接口鉴权,不好评价这个功能,我觉得Tars最需要的是一个易扩展的前置验证,就是在进入www.yourtars.com:8080的时候需要验证身份才可以进入,临时的方案可以使用resin自带的验证功能,缺点是不太利于集中管理权限。当然也可以自己在Tars管理页面前面套一层验证管理,这个成熟方案比较多,没什么特别要求的话大家选取合适和喜欢就好。
Tars的缺点
说了这么多Tars的亮点,下面也谈谈个人觉得Tars不太妥的地方。
一台服务器(物理机、虚拟机、云主机)不能部署同应用的同种Server
比如我都轻量级的微服务,AServer我想要部署4个进程,那么必须分布在4个不同的服务器上。资源有一定的浪费,好处是提高了服务可用性。
部署对于Tars新手门槛略高
虽然现在官方在推直接部署好的Tars云服务器,然后也有民间制作的docker,但是原生的安装步骤边边角角的坑实在太多了,尤其是对于linux不太熟悉的人。
小BUG依旧很多
Tars服务自带的配置文件框无法拉大,adminRegistry线程过多(官方已经修复),Tars自带服务QueryxxxServer的coredump(官方已经修复)……虽然都是不影响核心业务的小问题,总之还是需要不断的完善,不断的有人趟坑。
文档不全,缺少核心社区
官方文档很多都是早期腾讯内部就已存在的,开源以来更新的文档屈指可数,个人觉得少了几处比较关键的:客户端Future/Promise与Coroutine的使用样例、服务扩/缩容原理与注意事项、核心线程的协作关系;社区就不用说了,Tars群基本都是伸手党,官方少了一点带头作用。
最后希望Tars能越来越好!
(全文结束)
转载文章请注明出处:漫漫路 - lanindex.com
博主请问关于Tars框架的负载均衡中的权重轮询算法能不能举个场景,讲解一下?
Tars负载均衡有Hash、一致性Hash、轮询、权重三种方式(Hash是第一优先,如果设置了Hash会无视其他负载均衡设置)
Hash和一致性Hash原理就不多解释了,使用场景举一个简单例子,比如id:1,3,5->进入A机器处理,id:2,4,6进入B机器处理;
在id分布均匀的情况下,每台机器的负载都差不多。
轮询,跟字面意思一样,挨个来处理,负载分布最均匀,对于机器的增减不敏感。
为什么轮询分布均匀还需要Hash?原因是Hash有额外的好处:可以把同一id每次固定到同一台机器上处理。
权重,理解为可以调控的分布,对于有线上灰度的场景和机器资源不均匀的场景非常适用。
“Tars自带服务QueryxxxServer的coredump(我已经提交了Pull Request,但是没反应)”
这个在1月份已经merge了
…没注意,我修改下原文描述。
博主后面会对tars源码做个分析吗?
暂时没这个计划:D