golang gin http请求中断后停止后端的任务运行

一个常见的场景:有一个接口请求需要较长的时间(如5秒),那么,用户很可能等不及,直接就放弃了请求。

而这个接口的任务,如果在用户放弃请求后依然继续执行,那么就是浪费服务器的资源了。

所以,我们需要在得知请求中断后,主动结束耗时的任务。

这里面其实都是用到go 提供的一个上下文库context

然而这个问题的核心是如何得知http的请求是否中断。这个问题在网上居然没什么资料。。。

示例演示

首先,我模拟了两个任务:任务taskFunc1需要执行1秒,任务taskFunc2需要执行5秒:

// 该方法模拟进行任务,需要1秒钟
func taskFunc1(ctx context.Context, end chan string) {
	// 监听任务执行情况的协程
	result := make(chan string)
	// 启动协程执行任务
	go func() {
		time.Sleep(1 * time.Second)
		result <- "success" // 5秒后完成任务,将结果写入信道
	}()

	// 监听 context的结束信号和任务结束的信号,哪个先到就先执行哪个
	select {
	case <-ctx.Done():
		end <- "taskFunc1失败"
	case <-result:
		end <- "taskFunc1成功"

	}
}

// 该方法模拟进行任务,需要5秒钟,和上面是一样的
func taskFunc2(ctx context.Context, end chan string) {
	result := make(chan string)
	go func() {
		time.Sleep(5 * time.Second)
		result <- "success"
	}()

	select {
	case <-ctx.Done():
		end <- "taskFunc2失败"
	case <-result:
		end <- "taskFunc2成功"
	}
}

他们内容都是一样的,都有两个参数:

  1. ctx context.Context:这个参数就是上下文,对于http请求,当然http请求就应该作为这个上文,而任务就是这下文,上文如果主动中断了,那么下文也应该跟着中断
  2. end chan string:一个信道,为了通知主线程(如main方法)任务是否执行完成。

接下来实现一个接口:

// 1.一个测试接口
func Test1(c *gin.Context) {

	ctx := c.Request.Context() // 获取http请求的上下文对象

	// 主线程判断两个协程是否运行结束的信道
	end1 := make(chan string)
	end2 := make(chan string)
	// 启动两个协程分别完成任务
	go taskFunc1(ctx, end1)
	go taskFunc2(ctx, end2)
	// 监听两个协程是否结束
	err2, err1 := <-end2, <-end1
	fmt.Println(err1, err2)
}

当我们正常发起http请求后,后台打印的内容应该如下:
在这里插入图片描述
两个任务都成功执行,整个请求的时间大约是5秒,因为任务taskFunc2需要5秒钟。

然而,我用postman设置请求超时时间为3秒:
在这里插入图片描述
然后发起请求:
在这里插入图片描述
可以看到,taskFunc2失败了,整个请求时间是大约3秒,看到了吧,http请求中断后,我们的任务也主动结束了,而不是等taskFunc2执行完,这就是我想要的效果。

gin.Context

其实上面例子就为了证明,gin.Context是一个上文,能够告知下文当前的状态。之前我还不理解为什么他叫Context,现在知道了,其实一次http请求就被gin封装成了一个Context。

gin.Context和golang的context包,本质上是一个意思。

而获取这个上下文的方法就是:gin.Context.Request.Context()

适用场景

其实在此之前,我连context的存在都不知道,虽然用了gin很久,但是始终不理解它gin.Context的含义。

而这次去了解这玩意,是因为我需要用到mongodb和redis,这两个数据库的驱动分别是mongo-drivergo-redis,我就发现这两个驱动设计的api,都要传入参数context.Context

我就很纳闷呀,这个参数有什么用,还非得我传,每次还得给他来一下context.TODO()。所以,这两天就学习了一下这个context包。

于是就想到了gin的Context原来是这么来的,那么它一定也具有context的作用吧(然而百度搜来搜去都是一些原理和基础的介绍,很少有介绍这种细节的)。于是就有了上面的演示

于是现在也就知道了mongodb和redis要求传入这个参数的意义了:就是为了让我们能够主动结束它的任务,而不至于浪费资源继续请求数据,毕竟数据库资源是宝贵的。

当然,我们有必要每次都将gin的Context传入这些api吗?其实没有必要,因为大多数对数据库资源的请求也就是那么几十毫秒,为了节省这点资源,我们的代码需要多写很多,个人认为不需要做这种意义不大的事。

所以,我们还是针对一些特定的场景才使用,比如某些任务需要很长的时间等等。

所以,我们大多数时候,直接给api传入context.TODO()即可。

那么,为什么gorm对数据库的操作又不需要这个参数呢?还没了解过,以后再说

但是注意:在中间件中,无法使用gin.Context.Request.Context().Done()方法去检验请求是否终止,在中间件中,始终不会有返回值。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lsjweiyi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值