写给Go开发者的Tars教程-通信模式

2023-06-18
5分钟阅读时长

本篇为【写给Go开发者的Tars教程系列】第二篇


上一篇介绍了如何编写 Tars 的 idl,并使用 idl 生成了 TarsGo RPC 的代码,现在来看看如何编写客户端和服务端的代码

Tars 示例 IDL

module order {
    struct Order {
        1 require string id;
        2 optional vector<string> items;
        3 optional string description;
        4 require float price;
        5 optional string destination;
    };
    
    interface OrderManagement {
        Order getOrder(string orderId);
    };
};

生成 go 协议编解码代码

tars2go -outdir=. \
	-module=github.com/lbbniu/TarsGo-tutorial \
	proto/order.tars
order
├── order.go
└── OrderManagement.tars.go
proto
└── order.tars

server 实现

服务端开发

1、由 Tars 文件生成的 Go 代码中包含了 interface 的接口定义,它和我们定义的 idl 是吻合的

interface OrderManagement {
    Order getOrder(string orderId);
};
type OrderManagementServant interface {
	GetOrder(orderId string) (ret Order, err error)
}
type OrderManagementServantWithContext interface {
	GetOrder(tarsCtx context.Context, orderId string) (ret Order, err error)
}

2、我们的业务逻辑就是实现这个接口

package servant

import (
	"context"
	"net/http"

	"github.com/TarsCloud/TarsGo/tars"

	"github.com/lbbniu/TarsGo-tutorial/order"
)

var orders = make(map[string]order.Order)

type OrderCtx struct {
}

// 有context.Context实现服务端
var _ order.OrderManagementServantWithContext = (*OrderCtx)(nil)

func NewOrderCtx() *OrderCtx {
	o := &OrderCtx{}
	o.init()
	return o
}

func (o *OrderCtx) init() {
	orders["1"] = order.Order{
		Id:          "1",
		Price:       100,
		Items:       []string{"iPhone 11", "MacBook Pro"},
		Description: "MacBook Pro",
		Destination: "Beijing",
	}
}

func (o *OrderCtx) GetOrder(tarsCtx context.Context, orderId string) (ret order.Order, err error) {
	ord, exists := orders[orderId]
	if exists {
		return ord, nil
	}

	return ord, tars.Errorf(http.StatusNotFound, "Order does not exist. : ", orderId)
}

3、在实现完业务逻辑之后,我们可以创建并启动服务

package main

import (
	"github.com/TarsCloud/TarsGo/tars"

	"github.com/lbbniu/TarsGo-tutorial/internal/servant"
	"github.com/lbbniu/TarsGo-tutorial/order"
)

func main() {
	cfg := tars.GetServerConfig()
	imp := new(servant.Order)
	app := new(order.OrderManagement)
	app.AddServantWithContext(imp, cfg.App+"."+cfg.Server+".orderObj")
	tars.Run()
}

服务端配置详解

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

type adapterConfig struct {
	Endpoint endpoint.Endpoint
	Protocol string
	Obj      string
	Threads  int
}

type serverConfig struct {
	Node     string
	App      string
	Server   string
	LogPath  string
	LogSize  uint64
	LogNum   uint64
	LogLevel string
	Version  string
	LocalIP  string
	Local    string
	BasePath string
	DataPath string
	Config   string
	Notify   string
	Log      string
	Adapters map[string]adapterConfig

	Container   string
	Isdocker    bool
	Enableset   bool
	Setdivision string
	// add server timeout
	AcceptTimeout time.Duration
	ReadTimeout   time.Duration
	WriteTimeout  time.Duration
	HandleTimeout time.Duration
	IdleTimeout   time.Duration
	ZombieTimeout time.Duration
	QueueCap      int
	// add tcp config
	TCPReadBuffer  int
	TCPWriteBuffer int
	TCPNoDelay     bool
	// add routine number
	MaxInvoke int32
	// add adapter & report config
	PropertyReportInterval  time.Duration
	StatReportInterval      time.Duration
	MainLoopTicker          time.Duration
	StatReportChannelBufLen int32
	MaxPackageLength        int
	GracedownTimeout        time.Duration

	// tls
	CA           string
	Cert         string
	Key          string
	VerifyClient bool
	Ciphers      string

	SampleRate     float64
	SampleType     string
	SampleAddress  string
	SampleEncoding string
}

核心配置说明

  • 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
  • Adapters: 每个 adapter 适配器的指定配置.
  • Contianer: 保留供以后使用,用于存储容器名称.
  • Isdocker: 保留供以后使用,用于指定服务是否在容器内运行.
  • Enableset: 如果使用了 set,则为 True.
  • Setdivision: 指定哪个 set,如 gray.sz.*

编写启动配置文件config/config.conf

<tars>
    <application>
        <server>
            app=Test
            server=OrderServer
            local=tcp -h 127.0.0.1 -p 10027 -t 30000
            logpath=/tmp
            <Test.OrderServer.OrderObjAdapter>
                allow
                endpoint=tcp -h 127.0.0.1 -p 8080 -t 60000
                handlegroup=Test.OrderServer.OrderObjAdapter
                maxconns=200000
                protocol=tars
                queuecap=10000
                queuetimeout=60000
                servant=Test.OrderServer.OrderObj
                shmcap=0
                shmkey=0
                threads=1
            </Test.OrderServer.OrderObjAdapter>
        </server>
    </application>
</tars>

Test.OrderServer.OrderObjAdapter配置段为适配器对象绑定 ip 和端口,在服务端代码实现的例子中, app.AddServantWithContext(imp, cfg.App+"."+cfg.Server+".orderObj")完成 OrderObj 的适配器配置和实现的绑定。

服务端启动

go build -o OrderServer
./OrderServer --config=config/config.conf

client 实现

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

context.Content调用

package main

import (
	"fmt"
	
	"github.com/TarsCloud/TarsGo/tars"

	"github.com/lbbniu/TarsGo-tutorial/order"
)

func main() {
	comm := tars.GetCommunicator()
	client := new(order.OrderManagement)
    obj := "Test.OrderServer.OrderObj@tcp -h 127.0.0.1 -p 8080 -t 60000"
	comm.StringToProxy(obj, client)

	noCtxCall(client)
}

func noCtxCall(client *order.OrderManagement) {
	order, err := client.GetOrder("1")
	if err != nil {
		panic(err)
	}
	fmt.Printf("noctx: %+v\n", order)
}
  • github.com/lbbniu/TarsGo-tutorial/order 包是由 tars2go 工具使用 tars 协议文件生成的
  • comm: Communicator 用于与服务端进行通信,它应该只初始化一次并且是全局的。
  • obj: 对象名称,用于指定服务端的 ip 和端口。通常在"@“符号之前我们只需要对象名称。
  • client: 与 tars 文件中的接口关联的应用程序。 在本例中它是 order.OrderManagement
  • StringToProxy:StringToProxy 方法用于绑定对象名称和应用程序,如果不这样做,通信器将不知道谁与应用程序通信 。
  • client.GetOrder 用于调用 tars 文件中定义的方法,并返回 order 和 err。

客户端通信器

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

comm := tars.NewCommunicator()
comm.SetProperty("property", "tars.tarsproperty.PropertyObj")
comm.SetLocator("locator", "tars.tarsregistry.QueryObj@tcp -h ... -p ...")

描述:

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

客户端配置文件go定义

type clientConfig struct {
	Locator                 string
	Stat                    string
	Property                string
	ModuleName              string
	RefreshEndpointInterval int
	ReportInterval          int
	CheckStatusInterval     int
	KeepAliveInterval       int
	AsyncInvokeTimeout      int
    SyncInvokeTimeout      int
	// add client timeout
	ClientQueueLen     int
	ClientIdleTimeout  time.Duration
	ClientReadTimeout  time.Duration
	ClientWriteTimeout time.Duration
	ClientDialTimeout  time.Duration
	ReqDefaultTimeout  int32
	ObjQueueMax        int32
}

客户端通信器核心属性描述:

  • Locator:主控服务的地址必须采用“ip port”格式。 如果你不需要主控来查找服务,则无需配置此项.
  • AsyncInvokeTimeout:客户端异步调用的最大超时时间(以毫秒为单位),此配置的默认值为 5000.
  • SyncInvokeTimeout:客户端同步调用的最大超时时间(以毫秒为单位),此配置的默认值为 3000,现在没用于 tarsgo,后续会支持异步回调模式。
  • RefreshEndpointInterval:定期访问主控以获取信息的时间间隔(以毫秒为单位),此配置的默认值为一分钟.
  • Stat:在模块之间调用的服务的地址。 如果未配置此项,则表示将直接丢弃上报的数据.
  • Property:服务上报其属性的地址。 如果未配置,则表示将直接丢弃上报的数据.
  • ReportInterval:现在TarsGo中配置无效,后续会支持此参数。
  • ModuleName: 模块名称,默认值是可执行程序的名称。

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

<tars>
  <application>
    #The configuration required by the proxy
    <client>
        #address
        locator = tars.tarsregistry.QueryObj@tcp -h 127.0.0.1 -p 17890
        #The maximum timeout (in milliseconds) for synchronous calls.
        sync-invoke-timeout         = 3000
        #The maximum timeout (in milliseconds) for asynchronous calls.
        async-invoke-timeout        = 5000
        #The maximum timeout (in milliseconds) for synchronous calls.
        refresh-endpoint-interval   = 60000
        #Used for inter-module calls
        stat                        = tars.tarsstat.StatObj
        #Address used for attribute reporting
        property                    = tars.tarsproperty.PropertyObj
        #report time interval
        report-interval             = 60000
        #The module name
        modulename                  = Test.OrderServer
    </client>
  </application>
</tars>

context.Content调用

func ctxCall(client *order.OrderManagement) {
	order, err := client.GetOrderWithContext(context.Background(), "1")
	if err != nil {
		panic(err)
	}
	fmt.Printf("ctx: %+v\n", order)
}

单向调用(OneWay)

// 单向调用,无返回值,目前函数前面会有返回值,后续tars2go中会去掉
func oneWayCall(client *order.OrderManagement) {
	_, err := client.GetOrderOneWayWithContext(context.Background(), "1")
	if err != nil {
		panic(err)
	}
    fmt.Println("oneway")
}

取模哈希调用

// 取模调用
func modHashCall(client *order.OrderManagement) {
	ctx := current.ContextWithClientCurrent(context.Background())
	var hashCode uint32 = 1
	current.SetClientHash(ctx, int(tars.ModHash), hashCode)
	order, err := client.GetOrderWithContext(context.Background(), "1")
	if err != nil {
		panic(err)
	}
	fmt.Printf("ModHash: %+v\n", order)
}

一致性哈希调用

// 一致性哈希调用
func consistentHashCall(client *order.OrderManagement) {
	ctx := current.ContextWithClientCurrent(context.Background())
	var hashCode uint32 = 1
	current.SetClientHash(ctx, int(tars.ConsistentHash), hashCode)
	order, err := client.GetOrderWithContext(context.Background(), "1")
	if err != nil {
		panic(err)
	}
	fmt.Printf("ConsistentHash: %+v\n", order)
}

小结

经过上面的学习,我们一步步完成了协议、服务端、客户端的编写,现在可以轻松完成TarsGo项目的开发了。

不过上面手动创建项目的骨架结构的过程完全可以自动化,TarsGo提供了tarsgo脚手架一键创建项目。下面进行演示。

安装

go install github.com/TarsCloud/TarsGo/tars/tools/tarsgo@latest
  • 注意这里安装的是tarsgo脚手架,并非tars2go协议处理工具
  • 截止目前最新版本为 v1.3.6

帮助

直接执行tarsgo

tarsgo: An elegant toolkit for Go microservices.

Usage:
  tarsgo [command]

Available Commands:
  cmake       Create a service cmake template
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  make        Create a server make template
  upgrade     Auto upgrade tarsgo and tars2go

Flags:
  -h, --help      help for tarsgo
  -v, --version   version for tarsgo

Use "tarsgo [command] --help" for more information about a command.

tarsgo可以已创建cmakemake管理的项目,下面只展示make管理项目的创建演示,因为cmake也类似。

创建项目

命令

tarsgo make Test HelloGo Hello github.com/lbbniu/TarsGo-tutorial/HelloGo

执行结果

🚀 Creating server Test.HelloGo, please wait a moment.

go: creating new go.mod: module github.com/lbbniu/TarsGo-tutorial/HelloGo
go: to add module requirements and sums:
	go mod tidy

CREATED HelloGo/Hello.tars (165 bytes)
CREATED HelloGo/Hello_imp.go (602 bytes)
CREATED HelloGo/Makefile (154 bytes)
CREATED HelloGo/client/client.go (450 bytes)
CREATED HelloGo/config/config.conf (689 bytes)
CREATED HelloGo/debugtool/dumpstack.go (406 bytes)
CREATED HelloGo/go.mod (58 bytes)
CREATED HelloGo/main.go (520 bytes)
CREATED HelloGo/scripts/makefile.tars.gomod.mk (6308 bytes)
CREATED HelloGo/start.sh (63 bytes)

>>> Great!Done! You can jump in HelloGo
>>> Tips: After editing the Tars file, execute the following cmd to automatically generate golang files.
>>>       /bin/tars2go *.tars
$ cd HelloGo
$ ./start.sh
🤝 Thanks for using TarsGo
📚 Tutorial: https://doc.tarsyun.com/
  • 现在可以进入HelloGo目录执行./start.sh启动服务端程序了,是不是很简单啊。
  • 后续脚手架会优化骨架项目结构。

示例代码

https://github.com/lbbniu/TarsGo-tutorial.git

关注公众号获得更多精彩文章

公众号:程序员大兵