gin.ctx不建议在协程内使用
源代码/数据集已上传到 Github - posts
首先给个结论:千万不要再请求里面,把上下文直接传递到野生goroutine里面
先来看一个现象
代码如下:
1 | func(ctx *gin.Context) { |
请求开始生成一个uuid,设置进上下文中。开启一个协程,传入上下文和生成的uuid。协程内sleep一秒后读取上下文中的的uuid是否与与传入的uuid一致。
① 模拟请求,1秒1次
② 模拟请求,1秒2次
③ 模拟请求,压测模式
①中全部是一致
②中不一致少于一致
③出现大量不一致,压测尾部会有少量一致
造成的原因
Gin框架在gin.Run里面实现了调用http.ListenAndServe方法。因为gin.Engine实现了接口http.Handler,并且在http.ListenAndServe的第二位参数将engine传入,所以服务启动后的请求都由gin.ServeHTTP处理。
1 | func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
解释一下上面的代码。
- 当请求进来时,会从上下文的池中获取到一个上下文。当池中没有上下文,则会创建一个新的。
- 拿到上下文后,会给上下文初始化responseWriter
- 给上下文设置请求体数据
- 初始化上下文中的所有数据(除掉第3已设置的参数)
- 将上下文传入业务逻辑中
- 将上下文放回连接池中
上下文被放回sync.Pool时,没有被重置掉请求的数据。原请求一直持有该上下文的地址,当该上下文没有被二次取出时,原请求去获取上下文中的数据,可以取到一致的数据。
当请求频率间隔小于goroutine退出的时间,上下文在放回池中后又被下一个请求取出,设置上新的uuid,原goroutine拿着上下文的地址去取上下文中的数据,就导致了原goroutine读到了下一个请求的数据,导致出现了不一致。
随着并发量不断加大到一定的量,大量上下文不断被创建,被放进上下文池中,因为sync.Pool的分配策略,可能存在少部分上下文在一段时间内一直不会被取出来二次利用。就会导致,在高并发下,也会有少量的请求是读取到一致的数据。
当高并发接近尾声,不再有新增的请求进去,上下文池中的上下文不再被取出覆盖,又会出现一批一致的请求。
什么场景下可以往goroutine中传递上下文
goroutine非野生goroutine,goroutine在主请求中可以被管控到。在主请求的生命周期内,goroutine创建后退出。
比如下面这种
1 | var wg sync.WaitGroup |
如何避免上下文被野生goroutine使用
Gin框架在充分利用资源的同时也给服务带来了风险。所以应该尽量避免往goroutine中传递上下文。
必须要使用该怎么做
gin框架提供了copy方法为不得不使用上下文传递的场景提供支持。
复制后的上下文可以安全地在请求外的使用。
1 | // Copy returns a copy of the current context that can be safely used outside the request's scope. |
微信扫一扫,阅读/分享
edit this page last updated at 2024-04-22