news 2026/6/2 14:21:17

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/5/28 7:05:17

新能源汽车车载双向 OBC 的 MATLAB 仿真探索

新能源汽车车载双向OBC&#xff0c;PFC&#xff0c;LLC&#xff0c;V2G 双向 充电桩 电动汽车 车载充电机 充放电机 MATLAB仿真模型 &#xff08;1&#xff09;基于V2G技术的双向AC/DC、DC/DC充放电机MATLAB仿真模型&#xff1b; &#xff08;2&#xff09;前级电路为双向AC/D…

作者头像 李华
网站建设 2026/5/30 21:56:52

python基于Vue的电影院售票选座评价影评系统_5s356_django Flask pycharm项目

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

作者头像 李华
网站建设 2026/5/28 8:05:26

华为 ModelEngine Nexent 零代码平台智能体能力深度实践评测

前言 在 AI 技术加速落地的当下&#xff0c;智能体已从概念走向产业实践&#xff0c;但传统开发模式依赖复杂编程与 Prompt 工程&#xff0c;高门槛成为制约其规模化普及的核心瓶颈。而华为 ModelEngine Nexent 作为零代码级智能体开发平台&#xff0c;精准切中这一行业痛点&am…

作者头像 李华
网站建设 2026/5/28 8:05:20

python基于Vue的北京市公交线路管理系统_vi06l_django Flask pycharm项目

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

作者头像 李华
网站建设 2026/6/2 11:57:33

目录结构保留的JAVA文件夹上传实现思路

大文件传输系统解决方案 项目背景与需求分析 作为北京某软件公司项目负责人&#xff0c;我们近期面临一个技术挑战&#xff1a;在产品中集成一个高性能、高稳定性的超大文件传输系统。经过详细需求分析&#xff0c;我们确认以下核心需求&#xff1a; 大文件处理能力&#xf…

作者头像 李华
网站建设 2026/5/30 23:55:01

毕设项目 基于机器视觉的驾驶疲劳检测系统(源码+论文)

文章目录 0 前言1 项目运行效果2 课题背景3 Dlib人脸检测与特征提取3.1 简介3.2 Dlib优点 4 疲劳检测算法4.1 眼睛检测算法4.2 打哈欠检测算法4.3 点头检测算法 5 PyQt55.1 简介5.2相关界面代码 6 最后 0 前言 &#x1f525;这两年开始毕业设计和毕业答辩的要求和难度不断提升…

作者头像 李华