defer问题

来源:8-12 索引订单数据

404_

2023-12-04

// @Title 订单修改
// @Description 订单修改
// @Success 200 {object} app.Response
// @router / [put]
func (e *OrderController) Put(c *gin.Context) {
	var (
		model models.StoreOrder
		appG  = app.Gin{C: c}
	)
	httpCode, errCode := app.BindAndValid(c, &model)
	if errCode != constant.SUCCESS {
		appG.Response(httpCode, errCode, nil)
		return
	}
	orderService := order_service.Order{
		M: &model,
	}

	if err := orderService.Save(); err != nil {
		appG.Response(http.StatusInternalServerError, constant.FAIL_ADD_DATA, nil)
		return
	}

	//发送订单变更通知
	defer func() {
		_, order, err := orderService.GetOrderInfo()
		if err != nil {
			global.LOG.Error("GetOrderInfo error order_id", orderService.OrderId)
		} else {
			orderService.M = order
			orderService.OrderEvent(orderEnum.OperationUpdate)
		}
	}()

	appG.Response(http.StatusOK, constant.SUCCESS, nil)
}

我的问题:
老师说 用户接口对于时间比较敏感,因此将耗时的操作放到defer中
比如上面的代码:是在appG.Response(http.StatusOK, constant.SUCCESS, nil) 执行了之后 再执行defer函数中的内容吗?我本来也是这么理解的。调用Put方法,收到了200的http状态码,然后再执行defer函数中的内容。

但是:我写了一个例子,发现 只有在defer中的代码执行完成后,这个接口才返回,整个耗时并没有减少呀?

func Response(ctx *gin.Context) {
	ctx.JSON(http.StatusOK, gin.H{
		"status": "OK",
	})
	return
}

func Ping(ctx *gin.Context) {
	defer func() {
		time.Sleep(time.Millisecond * 10)
		fmt.Println("deffer......")
	}()
	Response(ctx)
}

func main() {
	engine := gin.Default()

	engine.GET("/ping", Ping)
	
	engine.Run(":8765")
}

写回答

1回答

少林码僧

2023-12-04

这里使用defer确实不能减少请求的总时间。http处理函数在整个请求处理链路中不是最外层的函数,它是被其他函数层层调用的。就比如下面这个例子:

func f1() {
    f2()
}

func f2() {
    defer func() {
       time.Sleep(time.Second)
    }()
}

f2就相当于上面的ping函数,它还被整个请求处理链路中的其他函数所调用,也就是需要等到f1函数执行完才会响应给客户端,f1执行完的前提是f2执行完,这就导致了需要f2的defer也执行完,ctx.JSON()方法只是将响应的状态码,Content-Type,Content-Body等信息写入到gin的上下文context中,并不表示调用了ctx.JSON()就会立即返回的。
所以这里如果希望发送变更通知的操作尽可能少的占用接口响应时间的话,有两种方式:

1.将defer改成go或者使用协程池执行发送操作,直接使用go可能会导致服务关闭时,还没执行完的这些goroutine就会随着主协程的退出而被销毁,从而导致变更消息丢失。使用协程池可以实现优雅关闭,当服务接收到退出信号后检查协程池中的协程数量,如果协程池中还有任务就进行等待延迟退出,知道协程池为空再退出;

2. 另一个比较优雅一点的实现是将订单状态变更的操作id和订单id发送到消息队列中,然后启动一个消费服务消费到订单状态变更的操作,这种实现需要记录每次订单状态变更时的操作日志


0
0

海量数据高并发场景,构建Go+ES8企业级搜索微服务

全新 ES8 配合技术组件,实现高性能搜索

267 学习 · 54 问题

查看课程