news 2026/1/19 7:30:12

Go context详解:超时控制与请求链路追踪

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go context详解:超时控制与请求链路追踪

刚写Go那会,context对我来说就是个"到处传的参数",函数签名里写上但也不知道有什么用。

后来线上出了几次问题才明白:context是Go并发控制的灵魂


context解决什么问题

想象一个场景:用户请求进来,你要调用3个下游服务,汇总结果返回。

funchandleRequest(w http.ResponseWriter,r*http.Request){result1:=callServiceA()result2:=callServiceB()result3:=callServiceC()response:=merge(result1,result2,result3)json.NewEncoder(w).Encode(response)}

问题来了:

  1. 如果用户取消请求(关闭浏览器),下游调用还在跑,浪费资源
  2. 如果ServiceA超时了,ServiceB和C还要继续等吗?
  3. 请求的一些上下文信息(用户ID、TraceID)怎么传下去?

context就是解决这些问题的。


context的四种创建方式

1. context.Background()

ctx:=context.Background()

空context,通常作为根context,在main函数或请求入口使用。

2. context.TODO()

ctx:=context.TODO()

也是空context,用于"还不确定用什么context"的场景。语义上表示待定。

3. context.WithCancel

ctx,cancel:=context.WithCancel(context.Background())defercancel()// 一定要调用,否则会泄漏gofunc(){// 做一些事情select{case<-ctx.Done():fmt.Println("被取消了")returncase<-time.After(10*time.Second):fmt.Println("完成")}}()// 某个条件触发取消cancel()

关键点

  • cancel()必须调用,通常defer cancel()
  • 取消是传播的,父context取消,所有子context都取消

4. context.WithTimeout / context.WithDeadline

// 3秒超时ctx,cancel:=context.WithTimeout(context.Background(),3*time.Second)defercancel()// 截止时间deadline:=time.Now().Add(3*time.Second)ctx,cancel:=context.WithDeadline(context.Background(),deadline)defercancel()

区别:

  • WithTimeout:相对时间(从现在开始多久)
  • WithDeadline:绝对时间(到什么时候)

超时控制实战

HTTP请求超时

funcfetchURL(ctx context.Context,urlstring)([]byte,error){req,err:=http.NewRequestWithContext(ctx,"GET",url,nil)iferr!=nil{returnnil,err}resp,err:=http.DefaultClient.Do(req)iferr!=nil{returnnil,err}deferresp.Body.Close()returnio.ReadAll(resp.Body)}// 使用funcmain(){ctx,cancel:=context.WithTimeout(context.Background(),5*time.Second)defercancel()data,err:=fetchURL(ctx,"https://example.com")iferr!=nil{iferrors.Is(err,context.DeadlineExceeded){fmt.Println("请求超时")}else{fmt.Println("请求失败:",err)}return}fmt.Println(string(data))}

数据库查询超时

funcqueryUser(ctx context.Context,db*sql.DB,userIDint)(*User,error){ctx,cancel:=context.WithTimeout(ctx,3*time.Second)defercancel()varuser User err:=db.QueryRowContext(ctx,"SELECT * FROM users WHERE id = ?",userID).Scan(&user.ID,&user.Name,&user.Email)iferr!=nil{iferrors.Is(err,context.DeadlineExceeded){returnnil,fmt.Errorf("查询超时")}returnnil,err}return&user,nil}

并发请求统一超时

funcfetchAll(ctx context.Context,urls[]string)([][]byte,error){ctx,cancel:=context.WithTimeout(ctx,10*time.Second)defercancel()results:=make([][]byte,len(urls))errs:=make([]error,len(urls))varwg sync.WaitGroupfori,url:=rangeurls{wg.Add(1)gofunc(iint,urlstring){deferwg.Done()results[i],errs[i]=fetchURL(ctx,url)}(i,url)}wg.Wait()// 检查是否超时ifctx.Err()!=nil{returnnil,ctx.Err()}// 检查各个请求的错误for_,err:=rangeerrs{iferr!=nil{returnnil,err}}returnresults,nil}

context传值

context.WithValue

typecontextKeystringconst(userIDKey contextKey="userID"traceIDKey contextKey="traceID")funcWithUserID(ctx context.Context,userIDint)context.Context{returncontext.WithValue(ctx,userIDKey,userID)}funcUserIDFromContext(ctx context.Context)(int,bool){userID,ok:=ctx.Value(userIDKey).(int)returnuserID,ok}// 使用funchandleRequest(ctx context.Context){ctx=WithUserID(ctx,12345)processOrder(ctx)}funcprocessOrder(ctx context.Context){userID,ok:=UserIDFromContext(ctx)if!ok{// 没有userIDreturn}fmt.Println("处理用户",userID,"的订单")}

最佳实践

  • key用自定义类型,避免冲突
  • 不要用context传业务数据,只传请求范围的元数据(TraceID、UserID等)
  • 提供封装函数,不要直接暴露key

链路追踪示例

typecontextKeystringconsttraceIDKey contextKey="traceID"funcWithTraceID(ctx context.Context)context.Context{traceID:=uuid.New().String()returncontext.WithValue(ctx,traceIDKey,traceID)}funcTraceID(ctx context.Context)string{iftraceID,ok:=ctx.Value(traceIDKey).(string);ok{returntraceID}return"unknown"}// 日志中间件funcLoggingMiddleware(next http.Handler)http.Handler{returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request){ctx:=WithTraceID(r.Context())log.Printf("[%s] %s %s 开始",TraceID(ctx),r.Method,r.URL.Path)start:=time.Now()next.ServeHTTP(w,r.WithContext(ctx))log.Printf("[%s] %s %s 完成 耗时:%v",TraceID(ctx),r.Method,r.URL.Path,time.Since(start))})}// 业务代码中使用funcqueryDB(ctx context.Context){log.Printf("[%s] 开始查询数据库",TraceID(ctx))// ...}

context在各层的传递

一个典型的Web服务:

// Handler层funcGetUser(w http.ResponseWriter,r*http.Request){ctx:=r.Context()userID:=chi.URLParam(r,"id")user,err:=userService.GetUser(ctx,userID)iferr!=nil{http.Error(w,err.Error(),500)return}json.NewEncoder(w).Encode(user)}// Service层func(s*UserService)GetUser(ctx context.Context,userIDstring)(*User,error){// 检查缓存user,err:=s.cache.Get(ctx,userID)iferr==nil{returnuser,nil}// 查数据库user,err=s.repo.FindByID(ctx,userID)iferr!=nil{returnnil,err}// 写缓存_=s.cache.Set(ctx,userID,user)returnuser,nil}// Repository层func(r*UserRepository)FindByID(ctx context.Context,userIDstring)(*User,error){varuser User err:=r.db.QueryRowContext(ctx,"SELECT id, name, email FROM users WHERE id = ?",userID).Scan(&user.ID,&user.Name,&user.Email)return&user,err}// Cache层func(c*Cache)Get(ctx context.Context,keystring)(*User,error){val,err:=c.redis.Get(ctx,key).Result()iferr!=nil{returnnil,err}varuser User json.Unmarshal([]byte(val),&user)return&user,nil}

规范:context作为第一个参数,命名为ctx。


常见错误

1. 忘记cancel

// 错误:会泄漏funcbad(){ctx,_:=context.WithTimeout(context.Background(),time.Second)// ...}// 正确funcgood(){ctx,cancel:=context.WithTimeout(context.Background(),time.Second)defercancel()// ...}

2. 把context存到struct里

// 错误typeServicestruct{ctx context.Context// 不要这样!}// 正确:context作为方法参数传递typeServicestruct{}func(s*Service)DoSomething(ctx context.Context)error{// ...}

context是请求级别的,不应该存储。

3. 使用context.Value传业务数据

// 错误:不要用context传业务参数ctx=context.WithValue(ctx,"order",order)funcprocessOrder(ctx context.Context){order:=ctx.Value("order").(Order)// ...}// 正确:业务数据走参数funcprocessOrder(ctx context.Context,order Order){// ...}

4. 不检查context是否取消

// 错误:长循环不检查ctxfuncprocess(ctx context.Context,items[]Item){for_,item:=rangeitems{doHeavyWork(item)// 即使ctx取消了也继续执行}}// 正确funcprocess(ctx context.Context,items[]Item)error{for_,item:=rangeitems{select{case<-ctx.Done():returnctx.Err()default:}doHeavyWork(item)}returnnil}

context的继承关系

Background (根) │ ├── WithCancel ────────────┐ │ │ │ cancel() 会取消所有子context │ ├── WithTimeout │ │ │ │ │ │ │ └── WithValue │ │ │ └── WithValue │ └── WithTimeout │ └── WithValue

特点:

  1. 取消向下传播,父取消,子都取消
  2. 取消不向上传播,子取消,父不受影响
  3. Value沿着链向上查找
funcmain(){ctx1:=context.Background()ctx2,cancel2:=context.WithCancel(ctx1)ctx3,cancel3:=context.WithTimeout(ctx2,time.Second)ctx4:=context.WithValue(ctx3,"key","value")// cancel2() 会同时取消 ctx2, ctx3, ctx4// cancel3() 只取消 ctx3, ctx4// ctx4.Value("key") 能取到// ctx3.Value("key") 取不到(Value不向上传播)defercancel2()defercancel3()}

超时时间的设计

经验值:

// HTTP对外接口总超时constAPITimeout=30*time.Second// 单个下游服务调用constServiceTimeout=5*time.Second// 数据库查询constDBTimeout=3*time.Second// Redis操作constCacheTimeout=100*time.Millisecond

超时递减

funchandleRequest(ctx context.Context){// 请求总超时30sctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()// 查缓存,100msdata,err:=queryCache(ctx)iferr==nil{returndata}// 查库,3s// 注意:这里不要再创建新的timeout context// 因为剩余时间可能不足3s了,用父context会自动继承剩余时间data,err=queryDB(ctx)iferr!=nil{returnerr}returndata}

总结

context的三个核心功能:

  1. 取消信号传播:cancel()
  2. 超时控制:WithTimeout/WithDeadline
  3. 请求级数据传递:WithValue

使用规范:

  • context作为第一个参数
  • 不要存到struct里
  • cancel必须调用
  • WithValue只传元数据,不传业务数据
  • 长操作要检查ctx.Done()

记住一句话:context是请求的生命周期控制器


有问题评论区聊。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/18 2:38:52

单相PWM整流器两种控制策略实现仿真分享

单相PWM整流器两种控制策略实现&#xff08;交流220V-直流350V整流&#xff09;仿真&#xff0c;分别采用直接电流控制&#xff08;PR控制器&#xff09;与虚拟dq控制&#xff08;PI控制器&#xff09;实现&#xff0c;两个仿真动稳态性能良好&#xff0c;附带仿真介绍文档&…

作者头像 李华
网站建设 2026/1/17 5:45:53

深度学习框架基于UNET __无人机建筑垃圾分割检测数据集 无人机建筑垃圾分割检测系统 自动识别非法倾倒建筑垃圾行为,辅助城管取证 _

&#x1f3d7;️ 数据集类别&#xff08;共 10 类&#xff09;类别英文名中文名称说明Debris建筑垃圾 / 碎屑建筑物倒塌后的瓦砾、混凝土块、钢筋等残骸Water水体河流、湖泊、积水区域等Building_No_Damage无损建筑完整未受损的建筑物Building_Minor_Damage轻微损坏建筑局部墙体…

作者头像 李华
网站建设 2026/1/17 23:56:13

python基于Vue的地方特色美食分享管理系统_gn195_django Flask pycharm项目

目录已开发项目效果实现截图关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;已开发项目效果实现截图 同行可拿货,招校园代理 ,本人源头供货商 python基于Vue的地方特色美食分享管理…

作者头像 李华
网站建设 2026/1/19 1:53:24

python基于Vue的华为电子数码手机商城交易平台秒杀_b6thv_django Flask pycharm项目

目录 已开发项目效果实现截图关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 已开发项目效果实现截图 同行可拿货,招校园代理 ,本人源头供货商 python基于Vue的华为电子数码手机商…

作者头像 李华
网站建设 2026/1/14 14:20:37

动态、静态障碍物局部路径规划(matlab) 自动驾驶 阿克曼转向系统 考虑车辆的运动学、几何学约束

动态、静态障碍物局部路径规划&#xff08;matlab&#xff09; 自动驾驶 阿克曼转向系统 考虑车辆的运动学、几何学约束 DWA算法一般用于局部路径规划&#xff0c;该算法在速度空间内采样线速度和角速度,并根据车辆的运动学模型预测其下一时间间隔的轨迹。对待评价轨迹进行评…

作者头像 李华
网站建设 2026/1/15 8:50:20

平衡业务连续性(BCP)与资源效率的ENOVIA许可证回收策略

平衡业务连续性&#xff08;BCP&#xff09;与资源效率的ENOVIA许可证回收策略作为一名企业IT部门的管理者&#xff0c;我常常面临一个棘手的问题&#xff1a;在日常运维中&#xff0c;如何在**保证业务连续性&#xff08;BCP&#xff09;**的前提下&#xff0c;高效管理ENOVIA…

作者头像 李华