写给Go开发者的Tars教程-通信模式
本篇为【写给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
可以已创建cmake
和make
管理的项目,下面只展示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