fiber框架中不存在全局上下文,每个http请求独享一个* fiber.ctx实例;正确做法是使用c.context()获取嵌套的go原生context.context,并通过withvalue等方法注入请求级数据,配合显式传递实现安全跨协程状态共享。

Fiber 框架里没有“全局上下文”这个概念,强行用全局变量或 static 存 Context 就是并发雷区。 它只有请求级的 *fiber.Ctx,且每个 HTTP 请求独享一个实例——这才是安全边界。所谓“全局”,实际是误用或混淆了 Go 原生 context.Context 和 Fiber 的 *fiber.Ctx。
为什么不能把 *fiber.Ctx 当成全局变量存
常见错误是写 var globalCtx *fiber.Ctx 或在 init() 里缓存它。这会导致:
- 多个请求共享同一
*fiber.Ctx实例,c.UserContext()、c.Locals、c.IP()等全部错乱 - 底层
fasthttp.RequestCtx被复用,Header、Body、Query 可能残留上个请求数据 - goroutine 安全彻底失效:两个并发请求同时读写
c.Locals["user"],结果不可预测
真正该用的上下文组合:fiber.Ctx + context.Context
Fiber 的 *fiber.Ctx 本身嵌套了一个 Go 原生 context.Context(可通过 c.Context() 获取),这才是你该扩展和传递的“上下文”:
-
c.Context()是请求生命周期绑定的,随*fiber.Ctx创建而创建、销毁而销毁 - 所有需要跨函数/协程传递的状态(如 trace_id、user_id、timeout),应通过
context.WithValue()注入这个 context,而不是塞进c.Locals - 下游调用 HTTP client、DB 查询时,必须显式传入
c.Context(),否则超时、取消、链路追踪全部失效 - 示例:
ctx := c.Context() ctx = context.WithValue(ctx, "user_id", userID) resp, err := http.DefaultClient.Do(req.WithContext(ctx))
c.Locals 是什么,什么时候能用
c.Locals 是 Fiber 提供的**请求内键值对存储**,仅限当前请求的 handler 链中使用,不是跨请求共享机制:
- 适合存中间件计算出的临时值,比如
c.Locals["auth_user"] = user,供后续 handler 直接取用 - 不能用于异步 goroutine:启动新 goroutine 后,
c.Locals不会自动继承,必须手动传参或显式拷贝 - 别把它当 context 替代品——
c.Locals不带取消信号、不传播 deadline、无法被外部 cancel - 如果要用,务必配合类型断言检查:
if user, ok := c.Locals["auth_user"].(*User); ok { ... }
跨服务调用时 Context 怎么透传
Fiber 默认不自动注入 OpenTelemetry 或自定义 context 到 outbound 请求,必须手动做:
- HTTP 请求:用
req.WithContext(c.Context()),再通过otel.GetTextMapPropagator().Inject()写入 trace header - 数据库驱动:确认 driver 支持 context(如 pgx/v5),所有
Query/Exec调用都传c.Context() - Redis/MQ 客户端:检查是否提供 WithContext 方法;没有的话,只能靠中间件提前设置 timeout 并封装
- 绝对不要依赖
http.Header.Set("trace-id", ...)手动传——漏掉 propagation 就断链
最易被忽略的一点:Fiber 的 *fiber.Ctx 是一次性的,不能保存引用、不能跨 goroutine 共享、不能缓存。所有“全局上下文”的需求,本质都是没分清请求边界和 context 生命周期。真要跨请求共享?那是 cache、DB 或分布式状态的事,不是 Context 的职责。











