news 2026/4/15 23:50:38

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/4/15 23:50:01

短视频多平台智能运营神器源码:AI剪辑+矩阵管理+百种工具赋能

温馨提示&#xff1a;文末有资源获取方式在短视频流量争夺白热化的今天&#xff0c;高效管理、智能创作与精准获客成为制胜关键。今天为大家深度剖析一套功能强大的短视频智能运营系统源码&#xff0c;它能将繁琐的运营工作自动化、智能化&#xff0c;是机构与创业者不可多得的…

作者头像 李华
网站建设 2026/4/15 23:50:35

python基于Vue的餐厅美食点餐信息管理系统设计_8u692_django Flask pycharm项目

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

作者头像 李华
网站建设 2026/4/8 15:24:58

网页编辑器自动转存Word图片到OSS的插件

项目需求分析与技术方案 作为项目负责人&#xff0c;针对企业网站后台管理系统富文本编辑器升级需求&#xff0c;结合信创国产化、多浏览器兼容、云存储集成等核心要求&#xff0c;现提出以下技术方案&#xff1a; 一、核心功能实现方案 Word/公众号内容粘贴功能 前端实现&…

作者头像 李华
网站建设 2026/4/12 19:16:46

HTML编辑器粘贴Word图片并压缩上传组件

各位道友好&#xff01;在下江西软件学院大三码农&#xff0c;江湖人称"Word图片驯兽师"&#xff0c;最近在搞UEditor的降龙十八掌——Word一键转存公式渲染多格式导入三连击&#xff01;现将毕生绝学&#xff08;其实就两周成果&#xff09;倾囊相授&#xff0c;附赠…

作者头像 李华
网站建设 2026/4/13 3:43:03

独家功能ERP进销存源码系统,基于PHP+MySQL的ERP进销存系统

温馨提示&#xff1a;文末有资源获取方式随着数字化转型浪潮席卷全球&#xff0c;企业需要一款技术先进、功能强大的进销存系统来支撑业务增长。我们推出这款基于PHPMySQL的ERP进销存系统源码&#xff0c;结合独家创新功能&#xff0c;为企业提供可靠的技术基石。它经过优化设计…

作者头像 李华