TARS RPC 通信框架|提供多种远程调用方式

Wesley13
• 阅读 557

TARS RPC 通信框架|提供多种远程调用方式

作者 | 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 框架部署,原理如下

TARS RPC 通信框架|提供多种远程调用方式

客户端通过调用 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/

或扫码获取:

TARS RPC 通信框架|提供多种远程调用方式

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这