在 Go 开发中,为什么总要传入 context?

2,352 阅读2分钟

一、背景

在golang使用redis的时候,会发现redis的函数调用中都需要传入一个变量:context

image.png

虽然这是redis-go的函数规定的,但是为什么需要传入这个变量?

除此之外,查看一些开源项目中,也经常会在一些连接,数据库操作中看见相关的参数传递。

二、为什么需要 context.Context

假设你要写一个 查询用户订单 的 API,逻辑很简单:

  1. 验证用户身份;
  2. 去数据库查订单;
  3. 调用物流服务拿到快递状态。

正常情况下没问题,但现实可能是这样的:

  • 用户点了查询,等了一会儿,觉得太慢,直接关掉了 App;
  • 服务器还在后台拼命查数据库、请求第三方接口;
  • 等处理完了,结果发现用户早就不需要了。

这种情况带来的问题:

  • 浪费资源:CPU、内存、数据库连接都被无效任务占着;
  • 拖垮性能:一旦并发多了,系统可能被这些“僵尸请求”压垮。

所以,Go 给我们准备了一个“传话筒”——context
只要上游任务取消了,消息会层层往下传,下游一看:哦,原来不用干了,那我立刻停掉。

这就是 context 的意义。

context的优点

  • 超时控制 (Timeout) :你可以设置一个上下文,当操作超过特定时间后自动取消。例如,如果数据库查询耗时过长,可以通过 context 自动取消,避免资源阻塞。

  • 取消信号 (Cancellation) :当一个请求被取消时(比如用户关闭了网页),你可以通过 context 向所有相关的下游操作(如数据库查询、网络请求)发送取消信号,让它们立即停止工作,释放资源。

  • 请求范围的数据传递 (Request-scoped Data)context 可以在函数调用链中传递一些请求特有的数据,例如请求 ID、认证信息等,而无需在每个函数签名中显式地传递这些参数。

  • 可追踪性 (Traceability) :在分布式系统中,context 常用于传递跟踪 ID(Trace ID),以便将一个请求在不同服务间的调用串联起来,便于问题排查。

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    // 设置一个 1 秒的超时
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()

    val, err := rdb.Get(ctx, "user:1001").Result()
    if err != nil {
        fmt.Println("查询失败:", err)
        return
    }
    fmt.Println("查询结果:", val)
}