写给Go开发者的Tars教程-超时控制

2024-01-10
2分钟阅读时长

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

第一篇:Tars协议基础

第二篇:通信模式

第三篇:拦截器

第四篇:错误处理

第五篇:context/status

第六篇:超时控制

本系列将持续更新,欢迎关注👏获取实时通知


导言

一个合理的超时时间是非常必要的,它能提高用户体验,提高服务器的整体性能,是服务治理的常见手段之一

为什么要设置超时

用户体验:很多RPC都是由用户侧发起,如果请求不设置超时时间或者超时时间不合理,会导致用户一直处于白屏或者请求中的状态,影响用户的体验

资源利用:一个RPC会占用两端(服务端与客户端)端口、cpu、内存等一系列的资源,不合理的超时时间会导致RPC占用的资源迟迟不能被释放,因而影响服务器稳定性

综上,一个合理的超时时间是非常必要的。在一些要求更高的服务中,我们还需要针对DNS解析、连接建立,读、写等设置更精细的超时时间。除了设置静态的超时时间,根据当前系统状态、服务链路等设置自适应的动态超时时间也是服务治理中一个常见的方案。

客户端的超时

连接超时

还记得我们怎么在客户端创建的么?

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)

// order, err := client.GetOrderWithContext(ctx, "1")
// if err != nil {
//    panic(err)
// }

如果目标地址tcp -h 127.0.0.1 -p 8080 -t 60000无法建立连接,comm.StringToProxy会出现错误或panic么?这里直接放结论:不会的,TarsGo只有在发生RPC调用时才会创建连接,并不会阻塞在这里,如果RPC调用时连接没有创建成功会在RPC调用中报错。

如果我们想控制连接创建时的超时时间该怎么做呢?

可以修改Tars服务的启动配置文件对应的配置项来完成,目前也只能通过配置文件进配置。默认链接超时时间为3000毫秒,如下所示配置值为毫秒时间戳。

<tars>
    <application>
        <client>
            # 默认值为3000毫秒
            clientdialtimeout=3000
        </client>
    </application>
</tars>

服务调用的超时

和上面连接超时的配置类似。也可以通过配置文件进行配置。如下所示:

<tars>
    <application>
        <client>
            # 默认值为3000毫秒
            async-invoke-timeout=3000
        </client>
    </application>
</tars>

同时XXXWithContext服务调用的第一个参数均是context.Context

所以也可以使用context.Context来控制服务调用的超时时间,目前的使用context.Context控制超时的方法并不遵循go语言的推荐做法,如下所示:

ctx := current.ContextWithClientCurrent(context.Background())
current.SetClientTimeout(ctx, 10000)
order, err := client.GetOrderWithContext(ctx, "1")
if err != nil {
    panic(err)
}

上面的只对当前本次调用生效。另外客户端实例也提供了修改调用超时时间的方法,如下所示:

client.TarsSetTimeout(10000)

上面方法对当前客户端的所有调用都生效,优先级低于通过context.Context传递的超时时间。

其他超时选项

另外客户端还提供了读写超时控制的相应配置选项,如下所示

<tars>
    <application>
        <client>
            # 读超时,默认值为100毫秒
            clientreadtimeout=100
            # 写超时,默认值为3000毫秒
            clientwritetimeout=3000
            # 连接空闲时间,默认值为60000毫秒
            clientidletimeout=60000
        </client>
    </application>
</tars>

拦截器中的超时

拦截器函数签名中timeout time.Duration,我们也可以在拦截器中修改超时时间。如下所示:

func orderClientFilter(next tars.ClientFilter) tars.ClientFilter {
	return func(ctx context.Context, msg *tars.Message, invoke tars.Invoke, timeout time.Duration) (err error) {
        timeout = 100*time.Second
		// Invoking the remote method
		err = next(ctx, msg, invoke, timeout)
		return err
	}
}

服务端的超时

连接超时

服务端也可以控制连接创建的超时时间,如果没有在设定的时间内建立连接,服务端就会主动断连,避免浪费服务端的端口、内存等资源

<tars>
    <application>
        <server>
            # 默认值为500毫秒
            accepttimeout=500
        </server>
    </application>
</tars>

服务实现中的超时

服务实现函数的第一个参数也是context.Context,所以我们可以在一些耗时操作前对context.Context进行判断:如果已经超时了,就没必要继续往下执行了。此时客户端也会收到上文提到过的超时error

截止目前所有tag版本中通过context.Context来控制超时存在问题,接下来会在下一个版本中优化此问题,优化PR:perf: optimize server-side handling of timeout control ,此PR合并后就可以通过当前描述的方式通过context.Context来控制处理超时。

// GetOrder 超时控制示例
func (o *OrderCtx) GetOrder(tarsCtx context.Context, orderId string) (ret order.Order, err error) {
	select {
	case <-tarsCtx.Done():
		return ret, tars.Errorf(http.StatusRequestTimeout, "Client cancelled, abandoning.")
	default:
	}

	ord, exists := orders[orderId]
	if exists {
		return ord, nil
	}

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

很多库都支持类似的操作,我们要做的就是把context.Context透传下去,当context.Context超时时就会提前结束操作了

db, err := gorm.Open()
if err != nil {
    panic("failed to connect database")
}

db.WithContext(ctx).Save(&users)

配置文件中的其他超时参数

在服务端的配置段还有一些其他超时配置参数如下所示:

<tars>
    <application>
        <server>
            # 读超时,默认值不超时
            readtimeout=0
            # 写超时,默认值不超时
            writetimeout=0
            # 处理超时,默认值不超时
            handletimeout=0
            # 连接空调超时,默认值60000毫秒
            idletimeout=60000
            # tarsnode keepalive超时,默认值10000
            zombietimeout=10000
            # 优雅退出超时,默认值60000毫秒
            gracedowntimeout=60000
        </server>
    </application>
</tars>

超时传递

一个正常的请求会涉及到多个服务的调用。从源头开始一个服务端不仅为上游服务提供服务,也作为下游的客户端

image-20240108232126522

如上的链路,如果当请求到达某一服务时,对于服务A来说已经超时了,那么就没有必要继续把请求传递下去了。这样可以最大限度的避免后续服务的资源浪费,提高系统的整体性能。

目前TarsGo还没有实现了这一特性,我们将计划在未来版本实现此超时传递功能。欢迎关注公众号订阅此功能的上线的通知。

总结

TarsGo提供了众多超时控制参数,以及超时控制方法,我们可以精细化的控制TarsGo中服务端、客户端两端的建连,调用,以及在拦截器中的超时时间。


👇 欢迎关注👇

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

公众号:程序员大兵