专栏首页后台全栈之路腾讯 Tars-Go 服务 Hello World——RPC 通信
原创

腾讯 Tars-Go 服务 Hello World——RPC 通信

引言

上一篇文章介绍了如何创建安装 TarsGo,同时也阐述了如何开始一个 Tars-Go HTTP 服务。本文就要开始 Tars-Go 的主力业务了:基于 Tars 自带的 rpc 协议,设计 Tars-Go 服务。

本文的内容大致思路与官方 Guick Start 相同,但例子会有所不同,同事对于一些坑也会解释得详细点。本文的代码可以在我的 GitHub repo 中找到。

本系列文章:


设计目标

上一篇文章中,我的 HTTP 服务器向前端返回一串 Json 字符串,其中包含了服务器时间。这一次,我设计一个服务来提供服务器时间;HTTP 服务则向这个新服务请求时间之后再返回给用户。

新的服务,我命名为 “amc.GoTarsServer.GoTarsObj”。前文提到 HTTP 服务的实例名称相对不太重要,但是供内部 rpc 调用的服务,其名称就很重要了,它是其他服务进行寻址的重要依据。


设计协议

Tars 框架的原生 rpc 调用是使用专门设计的 “Tars 协议”(文件后缀名 .tars)进行通信的。这个协议其实也不神秘,读者可以自行尝试一下、多看一些示例,很快就可以了解了。这里我按照我自己写的协议文件来说明吧:

// filename: DateTime.tars
module amc {
    struct GetTimeReq
    {
        0 optional  string  timeFmt;
    };

    struct GetTimeRsp
    {
        0 require   long    utcTimestamp;   // UTC UNIX timestamp
        1 require   long    localTimestamp;
        2 require   string  localTimeStr;
    };

    interface DateTime
    {
        int GetTime(GetTimeReq req, out GetTimeRsp rsp);
    };
};

协议解析

上面的协议中,其实包含了几个部分:

  • 文件名:没错,这也是协议的一部分。协议文件名其实也就是这个协议包的名称,转换工具会将其转为同名的源文件。
  • 模块名:参考 C++ 的命名空间,开发者可以自由利用这个模块名。我个人喜欢维持和 App 同名。
  • 接口和方法:接口(interface)内可以定义多个 rpc 方法
  • 复合数据类型:使用 struct 定义复合数据类型

所以,解读上面的协议,如下:

  1. 在接口 “DateTime” 下,定义了一个方法:GetTime
  2. GetTime 方法包含两个参数,分别是两个结构体。
    1. 入参中包含了变量名 timeFmt,表示以什么样的格式返回时间信息
    2. 出参包含了 UTC 时间戳、本地时间戳和时间字符串

个人建议

  • 在创建协议的时候,我喜欢以 int MethodName(MethodReq req, out MethodRsq rsp)的模式来命名,不论是否有入参和出参,方法中的 req 和 rsp 都会存在。这种设计方式比较适合未来的扩展,如果需要添加参数或返回信息,只需要在两个 struct 中添加即可。
  • 第一次创建协议的时候,如果入参都是必要的,那么建议均设置为 require 属性,表示该参数是必须的;但是在以后扩展协议时,新增参数应设置为 optional 属性,保证还未升级到新版本协议的 clients 仍能正常调用。

服务端代码设计

创建源码模板

首先,我们可以用 TarsGo 自带的工具首先生成工程模版:

$ cd $GOPATH/src/github.com/TarsCloud/TarsGo/tars/tools
$ chmod +x create_tars_server.sh
$ ./create_tars_server.sh amc GoTarsServer GoTars

执行脚本后,在相应目录下会生成必要的源文件:

$ cd ~/go/src/amc/GoTarsServer
$ ls -l
total 36
-rw-rw-r-- 1 centos centos  159 Jan  7 00:00 GoTars.tars
-rw-rw-r-- 1 centos centos  303 Jan  7 00:00 GoTarsImp.go
-rw-rw-r-- 1 centos centos  964 Jan  7 00:00 GoTarsServer.conf
-rw-rw-r-- 1 centos centos  422 Jan  7 00:00 GoTarsServer.go
drwxrwxr-x 2 centos centos 4096 Jan  7 00:00 client
drwxrwxr-x 2 centos centos 4096 Jan  7 00:00 debugtool
-rw-rw-r-- 1 centos centos  252 Jan  7 00:00 makefile
-rw-rw-r-- 1 centos centos   59 Jan  7 00:00 start.sh
drwxrwxr-x 2 centos centos 4096 Jan  7 00:00 vendor

其中 “GoTars.tars” 文件,我们就不需要了,用上面的 DateTime.tars 文件替换之。接着,我们使用 TarsGo 的工具,将协议文件转换为源文件:

$ cd ~/go/src/amc/GoTarsServer
$ tars2go DateTime.tars

执行后,tars2go 会在当前目录下,根据 .tars 文件中指定的 module 字段,生成一个新的目录。比如上面的协议文件,module 是 “amc”,那么 tars2go 就生成 Amc 目录。读者可以自行查看目录下的文件,如果 .tats 文件更新的话,需要再次执行 tats2go 命令刷新相应的文件——当然,我觉得完全可以调整 makefile 的逻辑来自动实现这一点。

实现协议

协议的实现,在 GoTarsImp.go 文件中实现。下面我只列出该文件中实现的主要部分:

package main

import (
	"fmt"
	"time"
	"strings"
	"github.com/TarsCloud/TarsGo/tars"
	amc "amc/GoTarsServer/Amc"				// Note 1
)

type GoTarsImp struct {}						// Note 2
var log = tars.GetLogger("logic")		// Note 3

func (imp *GoTarsImp) GetTime(req *amc.GetTimeReq, rsp *amc.GetTimeRsp) (int32, error) {	// Note 4
	// get timestamp
	utc_time := time.Now()
	local_time := utc_time.Local()

	// convert time string
	var time_str string
	if "" == (*req).TimeFmt {
		log.Debug("Use default time format")
		time_str = local_time.Format("01/02 15:04:05 2006")
	} else {
		log.Debug(fmt.Sprintf("Got format string: %s", (*req).TimeFmt))
		// ......
        // ......
		time_str = local_time.Format(time_str)
	}

	// construct response
	(*rsp).UtcTimestamp = utc_time.Unix()
	(*rsp).LocalTimestamp = local_time.Unix()
	(*rsp).LocalTimeStr = time_str
	return 0, nil
};

针对代码里的几个注释说明如下:

  1. 这里导入的包,就是前文 tars2go 所生成的 Amc 目录下的 go 文件。通过导入该包,我们就可以 access 到我们在前面的 .tars 文件中所定义的结构体和方法。这里其实是写了一个基于 $GOPATH 的绝对路径来存取该包。
  2. 定义了该 servant 的对象,供 server 调用——这个后文讲到 server 时会再提到。
  3. 使用 tars 自带的服务器本地日志模块。该模块需要传入一个文件名参数,模块会根据该文件名,在 /usr/local/app/tars/app_log/amc/GoTarsServer/ 目录下生成日志文件。比如我用的 log 文件名就是:amc.GoTarsServer_logic.log
  4. 这是 .tars 文件中 GetTime 的实现,它作为 GoTarsImp 对象的一个方法来实现。从返回值的角度,TarsGo rpc 方法的返回值除了协议中定义的(本例中是 int,对应于 Go 的 int32)之外,还有一个 error,如果需要的话,读者可以利用。

变量 / 方法名转换

细心的读者可能会发现,在上面的实现中,数据变量名和协议中定义的并不相同。是的,这就是刚转 Go 的开发者很容易遇到的坑之一:Go 语言是使用变量 / 方法 / 常量的命名方式来决定其可见性的,只有在首字母为大写的时候,该元素才能供外部访问。

笔者特意在 .tars 文件中,变量名采用了首字母小写的驼峰式命名法。读者可以看到,tars2go会自动将变量名和方法名的首字母改为大写,以保证其可见性。请开发者注意,否则会在编译时遇到未定义错误。

server 代码调用

现在让我们回到 GoTarsServer.go 文件。其实工具自动生成的代码就差不多了,唯一需要修改的是包导入的部分,改为如下所示:

import (
	"github.com/TarsCloud/TarsGo/tars"
	amc "amc/GoTarsServer/Amc"
)

其他不变。


客户端代码设计

这里我选择了上一篇文章中提到的 GoWebServer 来调用这个 tars 服务。这里我们就需要将已有的代码进行改造了。需要改造的代码是 GoWebImp.go 文件。这里我只摘出比较重要的 rpc 部分:

package main

import (
	// ......
	amc "amc/GoTarsServer/Amc"
)

// ......

func HttpRootHandler(w http.ResponseWriter, r *http.Request) {
	// ......

	comm = tars.NewCommunicator()
	app := new(amc.DateTime)
	obj := "amc.GoTarsServer.GoTarsObj"
	comm.SetProperty("locator", "tars.tarsregistry.QueryObj@tcp -h 10.0.4.11 -p 17890")		// Note 1

	req := amc.GetTimeReq{}								// Node 2
	rsp := amc.GetTimeRsp{}								// Node 2
	req.TimeFmt = "YYYY-MM-DD hh:mm:ss"

	comm.StringToProxy(obj, app)					// Note 3
	ret, err := app.GetTime(&req, &rsp)		// Node 3
	if err != nil {
		// ...... 系统错误处理
	} else {
		// ...... 从 rsp 中取出问题
	}

	w.Header().Set("Content-Type", "application/json;charset=utf-8")
	w.Write([]byte(......)		// 写入返回数据
	return
}

主要逻辑的说明如下:

  1. 选择路由,这里读者可以参照官方 quick start 文档的说明来解释。这里笔者就只采用正式用法。其中 10.0.4.1117890 是 Tars 主控 tarsregistry 的地址
  2. 准备用于承载参数和返回值的结构体
  3. 这两行就是实际的 rpc 调用

发布服务

服务发布的方法在前一篇文章已经说明了。GoWebServer 只需要在原有基础上做更新操作即可。本文的 GoTarsServer 也同理。不同的是在 "协议" 选项,应该选择 “TARS”。

服务发布、一切正常后,参照上一篇文章,再次访问 HTTP 服务,然后我们再查看 GoTarsServer 的 log,我们就可以看到两者已经成功地联系起来啦。


本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

原文发布于:https://cloud.tencent.com/developer/article/1382458

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • iOS开发常用之UI下拉刷新

    GuangdongQi
  • 聊聊flink LocalEnvironment的execute方法

    flink-java-1.6.2-sources.jar!/org/apache/flink/api/java/DataSet.java

    codecraft
  • SC2018 | 中国揭秘下一代超算,清华摘得竞赛总冠军

    全球超级计算大会(Supercomputing Conference,简称 SC)是国际超算领域的顶级会议,国际影响力巨大。在这次大会上,清华大学的团队还获得了...

    机器之心
  • 四方密码

    四方密码是一种对称式加密法,由法国人Felix Delastelle发明。这种方法将字母两个一组,然后采用多字母替换密码。四方密码用4个5×5的矩阵来加密。每个...

    安恒网络空间安全讲武堂
  • IOS开发之尺寸

      在移动端或者前端开发中,UI图通常是带标注的,指定某个控件的长宽等属性,一般UI给的是68px,72px这样的样式,但是我们在开发过程中通常又并不是完全按照...

    mukekeheart
  • 2018-11-20 CG Pipeline: 最佳图数据库性能对比--为您的CG生产数据服务

    https://www.google.com.ph/search?q=%E5%9B%BE%E6%95%B0%E6%8D%AE%E5%BA%93%E6%AF%94...

    Albert陈凯
  • 初识Go语言--(1)环境安装

    未来sky
  • 一个6年Java程序员的经验总结,写给还在迷茫中的朋友

    很多年前,刚刚从大学毕业的时候,很多公司来校招。其中最烂俗的一个面试问题是:“你希望你之后三到五年的发展是什么?”。我当时的标准回答是(原话):“成为在某一方面...

    美的让人心动
  • [ java 工具类] xml字符串解析成Map(DOM解析)

    Tencent JCoder
  • Influxdb中Select查询请求结果涉及到的一些数据结构

    相当于c里面的链表元素,itr指向下一个元素的指针,buf表示当前元素,即FloatPoint类型的链表的迭代器.

    扫帚的影子

扫码关注云+社区

领取腾讯云代金券