Golang三种方式实现超时退出

问题

前段时间发现线上有个服务接口,老是间歇性告警,有时候一天两三次,有时候一天都没有。异步

告警的逻辑是在一个接口中异步调用了另外一个HTTP接口,这个HTTP接口调用出现超时。可是我去问了负责这个HTTP接口的同窗,人家说他们的接口相应都是毫秒级别,还截图监控了,有图有真相,我还能说啥。ui

可是,超时是确实存在的,只是请求还可能没有到人家服务那边。spa

这种偶发性问题很差复现,偶尔来个告警也挺烦的,第一反应仍是先解决问题,思路也简单,失败后重试。code

解决方法

且不谈重试策略,先说说何时触发重试。cdn

咱们能够在接口请求出错抛出err的时候重试,可是这种很差控制,若是一个请求出去,十来秒都没有响应,则这个协程就要傻傻的等他报错才能重试,浪费生命啊~协程

因此结合上面同窗给出的毫秒级响应指标,能够设定一个超时时间,若是在指定超时时间后没有返回结果,则重试(这篇重试不是重点)。接口

func AsyncCall() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
	defer cancel()
	go func(ctx context.Context) {
		// 发送HTTP请求
	}()

	select {
	case <-ctx.Done():
		fmt.Println("call successfully!!!")
		return
	case <-time.After(time.Duration(time.Millisecond * 900)):
		fmt.Println("timeout!!!")
		return
	}
}
复制代码

说明string

一、经过context的WithTimeout设置一个有效时间为800毫秒的context。it

二、该context会在耗尽800毫秒后或者方法执行完成后结束,结束的时候会向通道ctx.Done发送信号。io

三、有人可能要问,你这里已经设置了context的有效时间,为何还要加上这个time.After呢?

这是由于该方法内的context是本身申明的,能够手动设置对应的超时时间,可是在大多数场景,这里的ctx是从上游一直传递过来的,对于上游传递过来的context还剩多少时间,咱们是不知道的,因此这时候经过time.After设置一个本身预期的超时时间就颇有必要了。

四、注意,这里要记得调用cancel(),否则即便提早执行完了,还要傻傻等到800毫秒后context才会被释放。

总结

上面的超时控制是搭配使用了ctx.Done和time.After。

Done通道负责监听context啥时候完事,若是在time.After设置的超时时间到了,你还没完事,那我就不等了,执行超时后的逻辑代码。

触类旁通

那么,除了上面这种超时控制策略,还有其余的套路吗?

有,可是大同小异。

第一种:使用time.NewTimer

func AsyncCall() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond * 800))
	defer cancel()
	timer := time.NewTimer(time.Duration(time.Millisecond * 900))

	go func(ctx context.Context) {
		// 发送HTTP请求
	}()

	select {
	case <-ctx.Done():
		timer.Stop()
		timer.Reset(time.Second)
		fmt.Println("call successfully!!!")
		return
	case <-timer.C:
		fmt.Println("timeout!!!")
		return
	}
}
复制代码

这里的主要区别是将time.After换成了time.NewTimer,也是一样的思路若是接口调用提早完成,则监听到Done信号,而后关闭定时器。

不然的话,会在指定的timer即900毫秒后执行超时后的业务逻辑。

第二种:使用通道

func AsyncCall() {
  ctx := context.Background()
	done := make(chan struct{}, 1)

	go func(ctx context.Context) {
		// 发送HTTP请求
		done <- struct{}{}
	}()

	select {
	case <-done:
		fmt.Println("call successfully!!!")
		return
	case <-time.After(time.Duration(800 * time.Millisecond)):
		fmt.Println("timeout!!!")
		return
	}
}
复制代码

一、这里主要利用通道能够在协程之间通讯的特色,当调用成功后,向done通道发送信号。

二、监听Done信号,若是在time.After超时时间以前接收到,则正常返回,不然走向time.After的超时逻辑,执行超时逻辑代码。

三、这里使用的是通道和time.After组合,也可使用通道和time.NewTimer组合。

总结

本篇主要介绍如何实现超时控制,主要有三种

一、context.WithTimeout/context.WithDeadline + time.After

二、context.WithTimeout/context.WithDeadline + time.NewTimer

三、channel + time.After/time.NewTimer

我的公众号JackieZheng,欢迎关注~

相关文章
相关标签/搜索