作者 | Eaton
导语 | TARS 中提供了一套高性能 RPC 通信框架,实现了服务间的高效通信。RPC 作为微服务的核心技术,支撑着移动互联网时代下不断增长的用户和海量的请求。为了满足更多的需求,TARS 支持了同步、异步等多种调用方式。本文将会详细阐述 TARS 中的几种远程调用方式。
目录
- RPC 简介
- TARS 服务寻址方式
- TARS 远程调用方式
- 同步调用
- 异步调用
- 单向调用
RPC 简介
RPC,即远程过程调用,是一种通过网络向远程计算机请求服务,而不需要了解底层网络技术的思想。通过屏蔽消息打包、服务寻址等远程网络通信细节,使远程调用就像调用本地函数或者本地对象的方法一样调用远程计算机的函数。
服务寻址是远程调用的基础。实现服务的远程调用,先要知道服务的地址,找到可调用的服务后,才能对服务发起有效的远程调用。下面我们先来了解一下 TARS 中的寻址方式。
TARS 服务寻址方式
TARS 服务的寻址方式,按照服务是否在主控节点 Registry
注册,通常可以分为两种方式:直接寻址和名字服务(主控路由服务)。
直接寻址,顾名思义,就是直接指定要调用的服务的地址,例如下面代码中,我们直接指定了要调用服务的具体地址,后续的调用都会访问这个服务。
auto prx = comm->stringToProxy<Demo::HelloPrx>(
"Test.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 8088");
prx->testHello("abc");
名字服务,即我们只需通过服务的名字就能调用某个服务,而不需要提供服务的具体地址,如下
auto prx = comm->stringToProxy<Demo::HelloPrx>("Test.HelloServer.HelloObj");
prx->testHello("abc");
由于代码中无需写具体IP配置,名字服务很大程度上提高了代码的可维护性。使用名字服务要求服务在主控节点 Registry
注册,即服务需要通过 TARS 框架部署,原理如下
客户端通过调用 stringToProxy
向主控请求要调用服务的地址列表。主控将返回服务地址列表给客户端,以供客户端发起服务调用。
远程调用方式
获取到服务地址列表后,客户端将发起服务调用。TARS 中提供了多种调用方式,使开发者能够根据具体的使用场景,选择合适的调用方式。
- 同步调用:发起调用后,等待调用返回结果,再继续执行后续逻辑;
- 异步调用:发起调用后,立刻执行后续逻辑,通过回调函数处理返回结果;
- 单向调用:只发起调用,不关心返回结果或被调服务是否接收;
- Hash 调用:同一用户的多次调用都请求同一服务器的服务。
让我们用TarsCpp的例子来看看这几种调用方式是如何使用的。本部分使用的例子中,调用的服务名字为 Demo.HelloServer.HelloObj
,其接口文件 Hello.tars
中接口定义如下
module Demo
{
interface Hello
{
int testHello(string req, out string rsp);
};
};
接口直接将传入的字符串返回,实现如下
int HelloServerImp::testHello(const string & req, string & rsp, tars::TarsCurrentPtr current)
{
rsp = req;
return 0;
}
关于服务的具体开发和部署流程,请参阅官方文档,这里不再赘述。
同步调用
同步调用是最常见的调用,也是最简单的调用。顾名思义,就是发起调用后,等待返回结果,能够满足大多数情况下的需求。
下面是一个客户端同步调用服务接口 testHello
的例子。调用过程和函数调用类似,通过服务通信代理对象 prx
调用服务的接口 testHello
,获取返回值。
#include <iostream>
#include "servant/Communicator.h"
#include "servant/ServantProxy.h"
#include "Hello.h" // Hello.tars 生成的头文件
using namespace std;
using namespace tars;
static string helloObj = "Demo.HelloServer.HelloObj";
int main(int argc, char *argv[])
{
CommunicatorPtr comm = new Communicator();
try
{
// 加载配置
TC_Config conf;
conf.parseFile("config.conf");
comm->setProperty(conf);
// 生成代理
auto prx = comm->stringToProxy<Demo::HelloPrx>(helloObj);
string rsp;
// 发起同步调用
int ret = prx->testHello("Hello", rsp);
if (ret == 0)
cout << "Call successfully: " << rsp << endl;
}
catch (exception &e)
{
cerr << "error: " << e.what() << endl;
}
catch(...)
{
cerr << "Unknown Error" << endl;
}
}
TC_Config
是 TARS 中提供的能够用于加载配置的工具类,相关使用方式可以参考文章 微服务开源框架TARS 之 基础组件。
编译执行这个例子,结果如下
$ ./Client
Call successfully: Hello
上述例子中,通过加载配置文件 config.conf
初始化了客户端的通信器 comm
,配置文件具体内容如下
<tars>
<application>
<client>
# 主控地址
locator = tars.tarsregistry.QueryObj@tcp -h 127.0.0.1 -t 60000 -p 17890
</client>
</application>
</tars>
可以看到,配置文件中我们配置了主控的地址。这样就像前面 TARS 寻址方式中介绍的,我们就不需要指定服务的地址,能够通过名字服务找到服务。
异步调用
同步调用很简单很常见,但并不能适应所有场景。但遇到调用的接口耗时比较长,或是接口返回结果对后续逻辑没有影响等情况时,使用同步调用会阻塞后续过程,影响应用性能,我们可以选择异步调用。
发起异步调用后,程序会立刻执行后续逻辑,而不关心调用的返回结果。异步调用后,一般会在调用结果返回后,通过注册回调函数对它处理。TarsCpp 中,回调对象包含两个回调函数,分别处理调用成功和调用失败的逻辑。接口 testHello
回调对象的定义如下:
// 定义回调方法
struct HelloCallback : public Demo::HelloPrxCallback
{
// 异步调用成功逻辑
virtual void callback_testHello(int ret, const string & rsp)
{
cout << rsp << endl;
}
// 异步调用失败逻辑
virtual void callback_testHello_exception(tars::Int32 ret)
{
cout << "callback exception: " << ret << endl;
}
};
修改前面的同步调用逻辑,我们可以通过调用 async_testHello
来进行异步调用,如下
...
// 加载配置
TC_Config conf;
conf.parseFile("config.conf");
comm->setProperty(conf);
// 生成代理
auto prx = comm->stringToProxy<Demo::HelloPrx>(helloObj);
// 定义远程回调对象
Demo::HelloPrxCallbackPtr cb = new HelloCallback;
// 发起异步调用
string req = "Hello";
prx->async_testHello(cb, req);
cout << "Call testHello async" << endl;
// 等待调用完成
sleep(1);
...
这里我们添加 sleep(1)
等待远程调用完成并执行回调逻辑。编译执行这个例子,结果如下
$ ./Client
Call testHello async
Hello
单向调用
顾名思义,单向调用就是单方面发起调用,只管发送数据,完全不关心调用返回结果。单向调用可以认为是不处理返回结果的异步调用的一种。
因此,单向调用的方式和异步调用的方式一样使用 async_testHello
即可,但不需要定义回调对象,传入 NULL
即可,如下
...
string req = "Hello";
// 发起单向调用
prx->async_testHello(NULL, req);
...
Hash 调用
前面我们介绍过 TARS 的名字服务,是通过主控获取对应服务的多个地址列表。因此一个服务可以部署多台,请求也是随机分发到这些服务器上。但是在某些场合下,希望某些请求总是在某一台服务器上,对于这种情况 TARS 提供了简单的方式实现,即 Hash 调用。
假设我们传入的参数 Hello
为用户 ID,通过 Hash 调用,每次传入的 ID 值为 Hello
时,每次Hello用户请求时,调用到的都是同一台服务器。
本文hash 调用的例子直接在同步调用的基础上进行修改。只需要在调用前链式调用 tars_hash
即可,修改的部分如下
...
#include "util/tc_hash_fun.h"
...
int ret = prx->tars_hash(hash_new<string>()("Hello"))->testHello("Hello", rsp);
...
注意: 这种方式是有一定风险的。如果后台某台服务器挂了,这些请求就会迁移到其他服务器。等到服务器恢复后,会再迁移回来。
tars_hash
参数必须是int
。对于string
类型来说,可以像上述例子一样,使用 TARS 基础库(util目录下)中的hash_new
方法,具体请参见 util/tc_hash_fun.h.
总结
TARS 除了支持直接寻址,还支持的名字服务路由的方式发现服务,提高了代码的可维护性。同时提供多种远程调用方式,开发者能够自由选择,满足多种场景下的需求。
TARS 可以在考虑到易用性和高性能的同时快速构建系统并自动生成代码,帮助开发人员和企业以微服务的方式快速构建自己稳定可靠的分布式应用,从而令开发人员只关注业务逻辑,提高运营效率。多语言、敏捷研发、高可用和高效运营的特性使 TARS 成为企业级产品。
TARS微服务助您数字化转型,欢迎访问:
TARS官网:https://TarsCloud.org
TARS源码:https://github.com/TarsCloud
Linux基金会官方微服务免费课程:https://www.edx.org/course/building-microservice-platforms-with-tars
获取《TARS官方培训电子书》:https://wj.qq.com/s2/6570357/3adb/
或扫码获取: