TarsGo

关于

  • Tarsgo是基于Golang编程语言使用Tars协议的高性能RPC框架。随着docker,k8s,etcd等容器化技术的兴起,Go语言变得流行起来。Go的goroutine并发机制使Go非常适合用于大规模高并发后端服务程序的开发。 Go语言具有接近C/C++的性能和接近python的生产力。在腾讯,一部分现有的C++开发人员正逐渐向Go转型,Tars作为广泛使用的RPC框架,现已支持C++/Java/Nodejs/Php,其与Go语言的结合已成为大势所趋。因此,在广大用户的呼声中我们推出了Tarsgo,并且已经将它应用于腾讯地图、应用宝、互联网+以及其他项目中。
  • 关于tars的整体架构和设计理念,请阅读 Tars介绍

功能特性

  • Tars2go工具: tars文件自动生成并转换为go语言,包含用go语言实现的RPC服务端/客户端代码
  • go语言版本的tars的序列化和反序列化包
  • 服务端支持心跳上报,统计监控上报,自定义命令处理,基础日志
  • 客户端支持直接连接和路由访问,自动重新连接,定期刷新节点状态以及支持UDP/TCP协议
  • 提供远程日志
  • 提供特性监控上报
  • 提供set分组
  • 提供 protocol buffers 支持, 详见 pb2tarsgo

安装

快速开始

性能数据

使用

服务端

  • 下面是一个完整的示例,用于说明如何使用tarsgo去构建服务端。

接口定义

在 $GOPATH/src下编写一个tars文件,如hello.tars , 比如 $GOPATH/src/TestApp/TestServer/hello.tars. 有关tars协议的更多详细信息, 请查看 https://github.com/TarsCloud/TarsTup/blob/master/docs-en/tars_tup.md

  1. module TestApp
  2. {
  3. interface Hello
  4. {
  5. int test();
  6. int testHello(string sReq, out string sRsp);
  7. };
  8. };

编译接口定义文件

构建 tars2go

编译并安装tars2go工具

  1. go install $GOPATH/src/github.com/TarsCloud/TarsGo/tars/tools/tars2go

编译tars文件并转成go文

  1. tars2go --outdir=./vendor hello.tars

接口实现

  1. package main
  2. import (
  3. "github.com/TarsCloud/TarsGo/tars"
  4. "TestApp"
  5. )
  6. type HelloImp struct {
  7. }
  8. //implete the Test interface
  9. func (imp *HelloImp) Test() (int32, error) {
  10. return 0, nil
  11. }
  12. //implete the testHello interface
  13. func (imp *HelloImp) TestHello(in string, out *string) (int32, error) {
  14. *out = in
  15. return 0, nil
  16. }
  17. func main() { //Init servant
  18. imp := new(HelloImp) //New Imp
  19. app := new(TestApp.Hello) //New init the A Tars
  20. cfg := tars.GetServerConfig() //Get Config File Object
  21. app.AddServant(imp, cfg.App+"."+cfg.Server+".HelloObj") //Register Servant
  22. tars.Run()
  23. }

说明:

  • HelloImp是结构体,你在里面实现Hello和Test接口, 注意Test和Hello必须以大写字母开头才能被导出,这是唯一与tars文件定义有所不同的地方。
  • TestApp.Hello是由tar2go工具生成的,它可以在./vendor/TestApp/Hello_IF.go中找到,其中包含一个名为TestApp的软件包,它与tars文件的TestApp模块一样。
  • tars.GetServerConfig()用于获得服务端配置。
  • cfg.App+”.”+cfg.Server+”.HelloObj” 是绑定到Servant的对象名,客户端将使用此名称访问服务端.

服务端配置

tars.GetServerConfig()返回服务端配置,其定义如下:

  1. type serverConfig struct {
  2. Node string
  3. App string
  4. Server string
  5. LogPath string
  6. LogSize string
  7. LogLevel string
  8. Version string
  9. LocalIP string
  10. BasePath string
  11. DataPath string
  12. config string
  13. notify string
  14. log string
  15. netThread int
  16. Adapters map[string]adapterConfig
  17. Container string
  18. Isdocker bool
  19. Enableset bool
  20. Setdivision string
  21. }
  • Node: 本地tarsnode地址,只有你使用tars平台部署才会使用这个参数.
  • APP: 应用名.
  • Server: 服务名.
  • LogPath: 保存日志的目录.
  • LogSize: 轮换日志的大小.
  • LogLevel: 轮换日志的级别.
  • Version: Tarsg的版本.
  • LocalIP: 本地ip地址.
  • BasePath: 二进制文件的基本路径.
  • DataPath: 一些缓存文件存储路径.
  • config: 获取配置的配置中心,如tars.tarsconfig.ConfigObj
  • notify: 上报通知报告的通知中心,如tars.tarsnotify.NotifyObj
  • log: 远程日志中心,如tars.tarslog.LogObj
  • netThread: 保留用于控制接收和发送包的go线程.
  • Adapters: 每个adapter适配器的指定配置.
  • Contianer: 保留供以后使用,用于存储容器名称.
  • Isdocker: 保留供以后使用,用于指定服务是否在容器内运行.
  • Enableset: 如果使用了set,则为True.
  • Setdivision: 指定哪个set,如gray.sz.*

如下是一个服务端配置的例子:

  1. <tars>
  2. <application>
  3. enableset=Y
  4. setdivision=gray.sz.*
  5. <server>
  6. node=tars.tarsnode.ServerObj@tcp -h 10.120.129.226 -p 19386 -t 60000
  7. app=TestApp
  8. server=HelloServer
  9. localip=10.120.129.226
  10. local=tcp -h 127.0.0.1 -p 20001 -t 3000
  11. basepath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/bin/
  12. datapath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/data/
  13. logpath=/usr/local/app/tars/app_log/
  14. logsize=10M
  15. config=tars.tarsconfig.ConfigObj
  16. notify=tars.tarsnotify.NotifyObj
  17. log=tars.tarslog.LogObj
  18. #timeout for deactiving , ms.
  19. deactivating-timeout=2000
  20. logLevel=DEBUG
  21. </server>
  22. </application>
  23. </tars>

适配器

适配器为每个对象绑定ip和端口.在服务端代码实现的例子中, app.AddServant(imp, cfg.App+”.”+cfg.Server+”.HelloObj”)完成HelloObj的适配器配置和实现的绑定。适配器的完整例子如下:

  1. <tars>
  2. <application>
  3. <server>
  4. #each adapter configuration
  5. <TestApp.HelloServer.HelloObjAdapter>
  6. #allow Ip for white list.
  7. allow
  8. # ip and port to listen on
  9. endpoint=tcp -h 10.120.129.226 -p 20001 -t 60000
  10. #handlegroup
  11. handlegroup=TestApp.HelloServer.HelloObjAdapter
  12. #max connection
  13. maxconns=200000
  14. #portocol, only tars for now.
  15. protocol=tars
  16. #max capbility in handle queue.
  17. queuecap=10000
  18. #timeout in ms for the request in the queue.
  19. queuetimeout=60000
  20. #servant
  21. servant=TestApp.HelloServer.HelloObj
  22. #threads in handle server side implement code. goroutine for golang.
  23. threads=5
  24. </TestApp.HelloServer.HelloObjAdapter>
  25. </server>
  26. </application>
  27. </tars>

服务端启动

如下命令用于启动服务端:

  1. ./HelloServer --config=config.conf

请参阅下面的config.conf的完整示例,稍后我们将解释客户端配置。

  1. <tars>
  2. <application>
  3. enableset=n
  4. setdivision=NULL
  5. <server>
  6. node=tars.tarsnode.ServerObj@tcp -h 10.120.129.226 -p 19386 -t 60000
  7. app=TestApp
  8. server=HelloServer
  9. localip=10.120.129.226
  10. local=tcp -h 127.0.0.1 -p 20001 -t 3000
  11. basepath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/bin/
  12. datapath=/usr/local/app/tars/tarsnode/data/TestApp.HelloServer/data/
  13. logpath=/usr/local/app/tars/app_log/
  14. logsize=10M
  15. config=tars.tarsconfig.ConfigObj
  16. notify=tars.tarsnotify.NotifyObj
  17. log=tars.tarslog.LogObj
  18. deactivating-timeout=2000
  19. logLevel=DEBUG
  20. <TestApp.HelloServer.HelloObjAdapter>
  21. allow
  22. endpoint=tcp -h 10.120.129.226 -p 20001 -t 60000
  23. handlegroup=TestApp.HelloServer.HelloObjAdapter
  24. maxconns=200000
  25. protocol=tars
  26. queuecap=10000
  27. queuetimeout=60000
  28. servant=TestApp.HelloServer.HelloObj
  29. threads=5
  30. </TestApp.HelloServer.HelloObjAdapter>
  31. </server>
  32. <client>
  33. locator=tars.tarsregistry.QueryObj@tcp -h 10.120.129.226 -p 17890
  34. sync-invoke-timeout=3000
  35. async-invoke-timeout=5000
  36. refresh-endpoint-interval=60000
  37. report-interval=60000
  38. sample-rate=100000
  39. max-sample-count=50
  40. asyncthread=3
  41. modulename=TestApp.HelloServer
  42. </client>
  43. </application>
  44. </tars>

客户端

用户可以轻松编写客户端代码,而无需编写任何指定协议的通信代码.

客户端例子

请参阅下面的一个客户端例子:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/TarsCloud/TarsGo/tars"
  5. "TestApp"
  6. )
  7. //tars.Communicator should only init once and be global
  8. var comm *tars.Communicator
  9. func main() {
  10. comm = tars.NewCommunicator()
  11. obj := "TestApp.TestServer.HelloObj@tcp -h 127.0.0.1 -p 10015 -t 60000"
  12. app := new(TestApp.Hello)
  13. comm.StringToProxy(obj, app)
  14. var req string="Hello Wold"
  15. var res string
  16. ret, err := app.TestHello(req, &out)
  17. if err != nil {
  18. fmt.Println(err)
  19. return
  20. }
  21. fmt.Println(ret, out)

说明:

  • TestApp包是由tars2go工具使用tars协议文件生成的.
  • comm: Communicator用于与服务端进行通信,它应该只初始化一次并且是全局的.
  • obj: 对象名称,用于指定服务端的ip和端口。通常在”@”符号之前我们只需要对象名称.
  • app: 与tars文件中的接口关联的应用程序。 在本例中它是TestApp.Hello.
  • StringToProxy: StringToProxy方法用于绑定对象名称和应用程序,如果不这样做,通信器将不知道谁与应用程序通信 .
  • req, res: 在tars文件中定义的输入和输出参数,用于在TestHello方法中.
  • app.TestHello用于调用tars文件中定义的方法,并返回ret和err.

通信器

通信器是为客户端发送和接收包的一组资源,其最终管理每个对象的socket通信。在一个程序中你只需要一个通信器。

  1. var comm *tars.Communicato
  2. comm = tars.NewCommunicator()
  3. comm.SetProperty("property", "tars.tarsproperty.PropertyObj")
  4. comm.SetProperty("locator", "tars.tarsregistry.QueryObj@tcp -h ... -p ...")

描述:

  • 通信器配置文件的格式将在后面描述.
  • 可以在没有配置文件的情况下配置通信器,并且所有参数都具有默认值.
  • 通信器也可以通过“SetProperty”方法直接初始化.
  • 如果您不需要配置文件,则必须自己设置locator参数.

通信器属性描述:

  • locator:主控服务的地址必须采用“ip port”格式。 如果你不需要主控来查找服务,则无需配置此项.
  • important async-invoke-timeout:客户端调用的最大超时时间(以毫秒为单位),此配置的默认值为3000.
  • sync-invoke-timeout:现在没用于tarsgo.
  • refresh-endpoint-interval:定期访问主控以获取信息的时间间隔(以毫秒为单位),此配置的默认值为一分钟.
  • stat:在模块之间调用的服务的地址。 如果未配置此项,则表示将直接丢弃上报的数据.
  • property:服务上报其属性的地址。 如果未配置,则表示将直接丢弃上报的数据.
  • report-interval:现在没用于tarsgo.
  • asyncthread: 已被tarsgo舍弃.
  • modulename: 模块名称,默认值是可执行程序的名称。

通信器配置文件的格式如下:

  1. <tars>
  2. <application>
  3. #The configuration required by the proxy
  4. <client>
  5. #address
  6. locator = tars.tarsregistry.QueryObj@tcp -h 127.0.0.1 -p 17890
  7. #The maximum timeout (in milliseconds) for synchronous calls.
  8. sync-invoke-timeout = 3000
  9. #The maximum timeout (in milliseconds) for asynchronous calls.
  10. async-invoke-timeout = 5000
  11. #The maximum timeout (in milliseconds) for synchronous calls.
  12. refresh-endpoint-interval = 60000
  13. #Used for inter-module calls
  14. stat = tars.tarsstat.StatObj
  15. #Address used for attribute reporting
  16. property = tars.tarsproperty.PropertyObj
  17. #report time interval
  18. report-interval = 60000
  19. #The number of threads that process asynchronous responses
  20. asyncthread = 3
  21. #The module name
  22. modulename = Test.HelloServer
  23. </client>
  24. </application>
  25. </tars>

超时控制

如果你想在客户端使用超时控制,请使用以ms为单位的TarsSetTimeout。

  1. app := new(TestApp.Hello)
  2. comm.StringToProxy(obj, app)
  3. app.TarsSetTimeout(3000)

接口调用

本节详细介绍了Tars客户端如何远程调用服务端。

首先,简要描述Tars客户端的寻址模式。 其次,它将介绍客户端的调用方法,包括但不限于单向调用,同步调用,异步调用,hash调用等。

寻址模式简介

Tars服务的寻址模式通常可以分为两种方式:服务名称在master上注册了,服务名称未在master上注册。 master是专用于注册服务节点信息的名字服务(路由服务)。

把服务名添加到名字服务中是通过操作管理平台实现。

对于未在master中注册的服务,可以将其分类为直接寻址,即在调用服务之前需要指定服务提供者的IP地址。 客户端需要在调用服务时指定HelloObj对象的特定地址,即Test.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 9985

Test.HelloServer.HelloObj: 对象名

tcp:Tcp协议

-h:指定主机地址,这里是127.0.0.1

-p:端口,这里是9985

如果HelloServer在两台服务器上运行,则应用程序初始化如下:

  1. obj:= "Test.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 9985:tcp -h 192.168.1.1 -p 9983"
  2. app := new(TestApp.Hello)
  3. comm.StringToProxy(obj, app)

HelloObj的地址设置为两个服务器的地址。 此时,请求将被分发到两个服务器(可以指定分发方法,这里不再介绍)。 如果一台服务器关闭,请求将自动分配给另一台服务器,服务器将定期重新启动。

对于在master中注册的服务,将根据服务名称对服务进行寻址。 当客户端请求服务时,它不需要指定HelloServer的特定地址,但是在生成通信器或初始化通信器时需要指定registry的地址。

以下通过设置通信器的参数显示主控的地址:

  1. var *tars.Communicator
  2. comm = tars.NewCommunicator()
  3. comm.SetProperty("locator", "tars.tarsregistry.QueryObj@tcp -h ... -p ...")

由于客户端需要依赖主控的地址,因此主控还必须具有容错能力。 主控的容错方法与上面相同,即指定了两个主控的地址。

单向调用

TODO. tarsgo暂未支持.

同步调用

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/TarsCloud/TarsGo/tars"
  5. "TestApp"
  6. )
  7. var *tars.Communicator
  8. func main() {
  9. comm = tars.NewCommunicator()
  10. obj := "TestApp.TestServer.HelloObj@tcp -h 127.0.0.1 -p 10015 -t 60000"
  11. app := new(TestApp.Hello)
  12. comm.StringToProxy(obj, app)
  13. var req string="Hello Wold"
  14. var res string
  15. ret, err := app.TestHello(req, &out)
  16. if err != nil {
  17. fmt.Println(err)
  18. return
  19. }
  20. fmt.Println(ret, out)

异步调用

tarsgo可以使用goroutine轻松使用异步调用。 与cpp不同,我们不需要实现回调函数。

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/TarsCloud/TarsGo/tars"
  5. "time"
  6. "TestApp"
  7. )
  8. var *tars.Communicator
  9. func main() {
  10. comm = tars.NewCommunicator()
  11. obj := "TestApp.TestServer.HelloObj@tcp -h 127.0.0.1 -p 10015 -t 60000"
  12. app := new(TestApp.Hello)
  13. comm.StringToProxy(obj, app)
  14. go func(){
  15. var req string="Hello Wold"
  16. var res string
  17. ret, err := app.TestHello(req, &out)
  18. if err != nil {
  19. fmt.Println(err)
  20. return
  21. }
  22. fmt.Println(ret, out)
  23. }()
  24. time.Sleep(1)

通过set调用

客户端可以通过set来调用服务端,只需要配置上文提到的配置文件,其中enableset置为y,setdivision比如设置为gray.sz. *。 有关更多详细信息,请参阅https://github.com/TarsCloud/Tars/blob/master/docs-en/tars_idc_set.md。 如果您想手动通过set调用,tarsgo将很快支持此功能。

Hash调用

由于可以部署多个服务端,因此客户端的请求会随机分发到服务端上,但在某些情况下,希望始终将某些请求发送到特定的服务端。 在这种情况下,Tars提供了一种简单的实现方法,称为hash调用。 Tarsgo很快将支持此功能。

tars定义的返回码

  1. //Define the return code given by the TARS service
  2. const int TARSSERVERSUCCESS = 0; //Server-side processing succeeded
  3. const int TARSSERVERDECODEERR = -1; //Server-side decoding exception
  4. const int TARSSERVERENCODEERR = -2; //Server-side encoding exception
  5. const int TARSSERVERNOFUNCERR = -3; //There is no such function on the server side
  6. const int TARSSERVERNOSERVANTERR = -4; //The server does not have the Servant object
  7. const int TARSSERVERRESETGRID = -5; // server grayscale state is inconsistent
  8. const int TARSSERVERQUEUETIMEOUT = -6; //server queue exceeds limit
  9. const int TARSASYNCCALLTIMEOUT = -7; // Asynchronous call timeout
  10. const int TARSINVOKETIMEOUT = -7; //call timeout
  11. const int TARSPROXYCONNECTERR = -8; //proxy link exception
  12. const int TARSSERVEROVERLOAD = -9; //Server overload, exceeding queue length
  13. const int TARSADAPTERNULL = -10; //The client routing is empty, the service does not exist or all services are down.
  14. const int TARSINVOKEBYINVALIDESET = -11; //The client calls the set rule illegally
  15. const int TARSCLIENTDECODEERR = -12; //Client decoding exception
  16. const int TARSSERVERUNKNOWNERR = -99; //The server is in an abnormal position

日志

使用tarsgo轮换日志的快速示例:

  1. TLOG := tars.GetLogger("TLOG")
  2. TLOG.Debug("Debug logging")

这将创建一个在tars/util/rogger中定义的*Rogger.Logger,并在调用GetLogger之后,会在config.conf中定义的Logpath下创建一个日志文件,其名称为cfg.App + “.” + cfg.Server + “_“ +名称,该日志文件将在100MB(默认)后轮换,最大轮换文件数为10(默认)。

如果你不想按文件大小轮换日志。 例如,你想要按天轮换,使用:

  1. TLOG := tars.GetDayLogger("TLOG",1)
  2. TLOG.Debug("Debug logging")

使用GetHourLogger(“TLOG”,1)按小时轮换日志。如果你想打日志到config.conf中定义的名为tars.tarslog.LogObj的远程服务器上,你不得不先配置一个日志服务器。可以在tars/protocol/res/LogF.tars中找到完整的tars文件定义,可以在Tencent/Tars/cpp/framework/LogServer中查找日志服务器。快速示例如下:

  1. TLOG := GetRemoteLogger("TLOG")
  2. TLOG.Debug("Debug logging")

如果你想设置日志等级,你可以在Tencent/Tars/web下的tars项目提供的OSS平台上设置它。 如果你想自定义你的日志,请在tars/util/logger,tars/logger.go 和tars/remotelogger.go中的查看更多细节。

服务管理

Tars服务框架支持动态接收命令来处理相关的业务逻辑,例如动态更新配置

tarsgo目前有tars.viewversion / tars.setloglevel管理命令。 用户可以从oss发送管理命令来查看版本或设置日志等级。

如果你想定义你自己的管理命令,请看下面的例子:

  1. func helloAdmin(who string ) (string, error) {
  2. return who, nil
  3. }
  4. tars.RegisterAdmin("tars.helloAdmin", helloAdmin)

然后你可以发送自定义的管理命令“tars.helloAdmin tarsgo”,tarsgo将在浏览器中显示。

举例:

  1. // A function should be in this format
  2. type adminFn func(string) (string, error)
  3. //then u should registry this function using
  4. func RegisterAdmin(name string, fn adminFn)

统计上报

上报统计信息是向Tars框架内的tarsstat上报耗时信息和其他信息。 无需用户开发,只需在程序初始化期间正确设置相关信息后,就可以在框架内自动报告(包括客户端和服务端)。

客户端调用上报接口后,会暂时将信息存储在内存中,当到达某个时间点时,会向tarsstat服务上报(默认为1分钟上报一次)。 我们将两个上报时间点之间的时间间隔称为统计间隔,在统计间隔中会执行诸如聚合和比较相同key的一些操作。 示例代码如下:

  1. //for error
  2. ReportStat(msg, 0, 1, 0)
  3. //for success
  4. ReportStat(msg, 1, 0, 0)
  5. //func ReportStat(msg *Message, succ int32, timeout int32, exec int32)
  6. //see more detail in tars/statf.go

描述:

  • 通常,我们不必关心统计上报,每次客户端调用服务端之后,无论成功与否,tarsgo框架都将会上报。 如果你设置正确,将在Web管理系统中显示成功率,失败率,平均耗时等。
  • 如果主服务部署在Web管理系统上,则无需定义Communicator、设置tarsregistry,tarsstat等的配置,该服务将会自动上报这些信息。
  • 如果未在Web管理系统上部署主服务或程序,则需要定义Communicator,设置tarsregistry,tarsstat等,以便您可以在Web管理系统上查看被调服务的服务监控。
  • 数据定期上报是可以在通信器的配置中设置。

异常上报

为了更好地监控,TARS框架支持直接向程序中的tarsnotify上报异常情况,并可在WEB管理页面上查看。

该框架提供了三个宏来上报不同类型的异常:

  1. tars.reportNotifyInfo("Get data from mysql error!")

Info是一个字符串,可以直接将字符串上报给tarsnotify。 上报的字符串可以在页面上看到,随后,我们可以根据上报的信息进行报警。

特性监控

为了便于业务统计,TARS框架还支持在Web管理平台上显示信息。

目前支持的统计类型包括:

  • Sum(sum) //计算每个上报值的总和
  • Average(avg) //计算每个上报值的均值
  • Distribution(distr) //计算每个上报的分布,其参数是一个列表,来计算每个区间的概率分布
  • Maximum(max) //计算每个上报值的最大值
  • Minimum(min) // 计算每个上报值的最小值
  • Count(count) //计算上报次数

示例代码如下:

  1. sum := tars.NewSum()
  2. count := tars.NewCount()
  3. max := tars.NewMax()
  4. min := tars.NewMin()
  5. d := []int{10, 20, 30, 50}
  6. distr := tars.NewDistr(d)
  7. p := tars.CreatePropertyReport("testproperty", sum, count, max, min, distr)
  8. for i := 0; i < 5; i++ {
  9. v := rand.Intn(100)
  10. p.Report(v)
  11. }

描述:

  • 定期上报数据,可以在通信器的配置中设置,目前是每分钟上报一次;
  • 创建一个PropertyReportPtr函数:参数createPropertyReport可以是任何统计方法的集合,示例中使用六种统计方法,通常只需要使用一个或两个;
  • 注意,当你在调用createPropertyReport时,必须在启用服务后创建并保存所创建的对象,然后只需将对象上报,不要在你每次使用时都创建它。

远程配置

用户可以从OSS设置远程配置。详情请查看https://github.com/TarsCloud/TarsFramework/blob/master/docs-en/tars_config.md . 如下示例用于说明如何使用此api从远程获取配置文件。

  1. import "github.com/TarsCloud/TarsGo/tars"
  2. ...
  3. cfg := tars.GetServerConfig()
  4. remoteConf := tars.NewRConf(cfg.App, cfg.Server, cfg.BasePath)
  5. config, _ := remoteConf.GetConfig("test.conf")
  6. ...

setting.go

tars包中的setting.go用于控制tarsgo性能和特性。有些选项应该从Getserverconfig()中更新。

  1. //number of woker routine to handle client request
  2. //zero means no contorl ,just one goroutine for a client request.
  3. //runtime.NumCpu() usually best performance in the benchmark.
  4. var MaxInvoke int = 0
  5. const (
  6. //for now ,some option shuold update from remote config
  7. //version
  8. TarsVsersion string = "1.0.0"
  9. //server
  10. AcceptTimeout time.Duration = 500 * time.Millisecond
  11. //zero for not set read deadline for Conn (better performance)
  12. ReadTimeout time.Duration = 0 * time.Millisecond
  13. //zero for not set write deadline for Conn (better performance)
  14. WriteTimeout time.Duration = 0 * time.Millisecond
  15. //zero for not set deadline for invoke user interface (better performance)
  16. HandleTimeout time.Duration = 0 * time.Millisecond
  17. IdleTimeout time.Duration = 600000 * time.Millisecond
  18. ZombileTimeout time.Duration = time.Second * 10
  19. QueueCap int = 10000000
  20. //client
  21. ClientQueueLen int = 10000
  22. ClientIdleTimeout time.Duration = time.Second * 600
  23. ClientReadTimeout time.Duration = time.Millisecond * 100
  24. ClientWriteTimeout time.Duration = time.Millisecond * 3000
  25. ReqDefaultTimeout int32 = 3000
  26. ObjQueueMax int32 = 10000
  27. //report
  28. PropertyReportInterval time.Duration = 10 * time.Second
  29. StatReportInterval time.Duration = 10 * time.Second
  30. //mainloop
  31. MainLoopTicker time.Duration = 10 * time.Second
  32. //adapter
  33. AdapterProxyTicker time.Duration = 10 * time.Second
  34. AdapterProxyResetCount int = 5
  35. //communicator default ,update from remote config
  36. refreshEndpointInterval int = 60000
  37. reportInterval int = 10000
  38. AsyncInvokeTimeout int = 3000
  39. //tcp network config
  40. TCPReadBuffer = 128 * 1024 * 1024
  41. TCPWriteBuffer = 128 * 1024 * 1024
  42. TCPNoDelay = false
  43. )

HTTP支持

目前的tar.TarsHttpMux和golang内置http.ServeMux使用方式是一致的,其中pattern参数做为监控数据的接口名,后续会参考github.com/gorilla/mux实现功能更强大的路由功能。

具体实现可参考下面的例子:

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/TarsCloud/TarsGo/tars"
  5. )
  6. func main() {
  7. mux := &tars.TarsHttpMux{}
  8. mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  9. w.Write([]byte("Hello tafgo"))
  10. })
  11. cfg := tars.GetServerConfig()
  12. tars.AddHttpServant(mux, cfg.App+"."+cfg.Server+".HttpObj") //Register http server
  13. tars.Run()
  14. }

Context 支持

TarsGo 之前在生成的客户端代码,或者用户传入的实现代码里面,都没有使用context。 这使得我们想传递一些框架的信息,比如客户端ip,端口等,或者用户传递一些调用链的信息给框架,都很难于实现。 通过接口的一次重构,支持了context,这些上下文的信息,将都通过context来实现。 这次重构为了兼容老的用户行为,采用了完全兼容的设计。

服务端使用context

  1. type ContextTestImp struct {
  2. }
  3. //只需在接口上添加 ctx context.Context参数
  4. func (imp *ContextTestImp) Add(ctx context.Context, a int32, b int32, c *int32) (int32, error) {
  5. //我们可以通过context 获取框架传递的信息,比如下面的获取ip, 甚至返回一些信息给框架,详见tars/util/current下面的接口
  6. ip, ok := current.GetClientIPFromContext(ctx)
  7. if !ok {
  8. logger.Error("Error getting ip from context")
  9. }
  10. return 0, nil
  11. }
  12. //以前使用AddServant ,现在只需改成AddServantWithContext
  13. app.AddServantWithContext(imp, cfg.App+"."+cfg.Server+".ContextTestObj")

客户端使用context

    ctx := context.Background()
    c := make(map[string]string)
    c["a"] = "b" 
//以前使用app.Add 进行客户端调用,这里只要变成app.AddWithContext ,就可以传递context给框架,如果要设置给tars请求的context
//可以多传入参数,比如c,参数c是可选的,格式是 ...[string]string
    ret, err := app.AddWithContext(ctx, i, i*2, &out, c)

服务端和客户端的完整例子,详见 TarGo/examples

filter机制(插件) 和 zipkin opentracing

为了支持用户编写插件,我们支持了filter机制,分为服务端的过滤器和客户端过滤器

//服务端过滤器, 传入dispatch,和f, 用于调用用户代码, req, 和resp为传入的用户请求和服务端相应包体
type ServerFilter func(ctx context.Context, d Dispatch, f interface{}, req *requestf.RequestPacket, resp *requestf.ResponsePacket, withContext bool) (err error)
//客户端过滤器, 传入msg(包含obj信息,adapter信息,req和resp包体), 还有用户设定的调用超时
type ClientFilter func(ctx context.Context, msg *Message, invoke Invoke, timeout time.Duration) (err error)
//注册服务端过滤器
//func RegisterServerFilter(f ServerFilter)
//注册客户端过滤器
//func RegisterClientFilter(f ClientFilter)

有了过滤器,我们就能对服务端和客户端的请求做一些过滤,比如使用 hook用于分布式追踪的opentracing 的span。 我们来看下客户端filter的例子:

//生成客户端tars filter,通过注册这个filter来实现span的注入
func ZipkinClientFilter() tars.ClientFilter {
    return func(ctx context.Context, msg *tars.Message, invoke tars.Invoke, timeout time.Duration) (err error) {
        var pCtx opentracing.SpanContext
        req := msg.Req
        //先从客户端调用的context 里面看下有没有传递来调用链的信息,
        //如果有,则以这个做为父span,如果没有,则起一个新的span,span名字是RPC请求的函数名
        if parent := opentracing.SpanFromContext(ctx); parent != nil {
            pCtx = parent.Context()
        }
        cSpan := opentracing.GlobalTracer().StartSpan(
            req.SFuncName,
            opentracing.ChildOf(pCtx),
            ext.SpanKindRPCClient,
        )
        defer cSpan.Finish()
        cfg := tars.GetServerConfig()

        //设置span的信息,比如我们调用的客户端的ip地址,请求的接口,方法,协议,客户端版本等信息
        cSpan.SetTag("client.ipv4", cfg.LocalIP)
        cSpan.SetTag("tars.interface", req.SServantName)
        cSpan.SetTag("tars.method", req.SFuncName)
        cSpan.SetTag("tars.protocol", "tars")
        cSpan.SetTag("tars.client.version", tars.TarsVersion)

        //将span注入到 请求包体的  Status里面,status 是一个map[strint]string 的结构体
        if req.Status != nil {
            err = opentracing.GlobalTracer().Inject(cSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(req.Status))
            if err != nil {
                logger.Error("inject span to status error:", err)
            }
        } else {
            s := make(map[string]string)
            err = opentracing.GlobalTracer().Inject(cSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(s))
            if err != nil {
                logger.Error("inject span to status error:", err)
            } else {
                req.Status = s
            }
        }
        //没什么其他需要修改的,就进行客户端调用
        err = invoke(ctx, msg, timeout)
        if err != nil {
            //调用错误,则记录span的错误信息
            ext.Error.Set(cSpan, true)
            cSpan.LogFields(oplog.String("event", "error"), oplog.String("message", err.Error()))
        }

        return err
    }

服务端也会注册一个filter,主要功能就是从request包体的status 提取调用链的上下文,以这个作为父span,进行调用信息的记录。

详细代码参见 TarsGo/tars/plugin/zipkintracing 完整的zipkin tracing的客户端和服务端例子,详见 TarsGo/examples下面的ZipkinTraceClient和ZipkinTraceServer