Golang context 的使用和应用
· 4 min read
1. 背景
context
是 Go 标准库中的一个核心包,用于在多个 Goroutine 之间传递请求相关的上下文信息(如超时、取消信号、键值数据等)。它常用于高并发场景(如 HTTP 服务、RPC 调用、数据库操作等),帮助优雅地控制 Goroutine 的生命周期。
2. 核心功能
- 超时控制:通过
context.WithTimeout
设置截止时间,避免请求长时间阻塞。 - 取消传播:通过
context.WithCancel
发送取消信号,通知所有相关 Goroutine 退出。 - 数据传递:通过
context.WithValue
在调用链中安全传递键值对(但需谨慎使用)。
3. 典型应用场景
- HTTP 服务:为每个请求创建
ctx
,传递请求 ID、超时设置等。 - 数据库/API 调用:将
ctx
传递给底层调用,确保资源及时释放。 - 分布式追踪:通过
ctx
传递链路跟踪信息(如 OpenTelemetry)。
4. 代码示例
案例 1: 简单
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 创建一个带有超时的 context(1秒后自动取消)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() // 确保释放资源
// 模拟一个耗时操作
go func() {
select {
case <-time.After(2 * time.Second):
fmt.Println("任务完成")
case <-ctx.Done(): // 监听取消信号
fmt.Println("任务取消:", ctx.Err())
}
}()
// 等待结果
time.Sleep(2 * time.Second)
}
案例 2: 分布式服务调用链,数据库写作,可取消管道
package main
import (
"context"
"fmt"
"log"
"math/rand"
"sync"
"time"
)
// 模拟服务返回结构
type Result struct {
ServiceName string
Data string
Err error
}
// 生成追踪ID
func generateTraceID() string {
return fmt.Sprintf("trace-%d-%d", time.Now().UnixNano(), rand.Intn(1000))
}
// 模拟认证服务
func callAuthService(ctx context.Context, wg *sync.WaitGroup, results chan<- Result) {
defer wg.Done()
// 从上下文获取追踪ID
traceID := ctx.Value("trace_id").(string)
log.Printf("[%s] 开始调用认证服务", traceID)
select {
case <-time.After(time.Duration(rand.Intn(2000)) * time.Millisecond): // 随机耗时
results <- Result{
ServiceName: "auth",
Data: "user_authenticated",
}
case <-ctx.Done():
results <- Result{
ServiceName: "auth",
Err: fmt.Errorf("认证服务取消: %v", ctx.Err()),
}
}
}
// 模拟数据库调用
func callDB(ctx context.Context, wg *sync.WaitGroup, results chan<- Result) {
defer wg.Done()
traceID := ctx.Value("trace_id").(string)
log.Printf("[%s] 开始调用数据库", traceID)
select {
case <-time.After(time.Duration(rand.Intn(3000)) * time.Millisecond):
results <- Result{
ServiceName: "database",
Data: "query_result",
}
case <-ctx.Done():
results <- Result{
ServiceName: "database",
Err: fmt.Errorf("数据库调用取消: %v", ctx.Err()),
}
}
}
func HandleRequest(ctx context.Context) {
// 注入追踪ID(实际项目可用OpenTelemetry等)
traceID := generateTraceID()
ctx = context.WithValue(ctx, "trace_id", traceID)
log.Printf("[%s] 请求开始", traceID)
// 设置总超时(实际根据业务需求调整)
ctx, cancel := context.WithTimeout(ctx, 1500*time.Millisecond)
defer cancel()
// 并发调用服务
var wg sync.WaitGroup
results := make(chan Result, 2) // 缓冲通道防止goroutine泄漏
wg.Add(2)
go callAuthService(ctx, &wg, results)
go callDB(ctx, &wg, results)
// 异步关闭结果通道
go func() {
wg.Wait()
close(results)
}()
// 处理结果
successCount := 0
for r := range results {
if r.Err != nil {
log.Printf("[%s] %s 失败: %v", traceID, r.ServiceName, r.Err)
cancel() // 触发快速失败
continue
}
successCount++
log.Printf("[%s] %s 成功: %s", traceID, r.ServiceName, r.Data)
}
if successCount == 2 {
log.Printf("[%s] 所有服务调用成功", traceID)
} else {
log.Printf("[%s] 部分服务调用失败", traceID)
}
}
func main() {
rand.Seed(time.Now().UnixNano())
// 模拟10次请求
for i := 0; i < 10; i++ {
HandleRequest(context.Background())
time.Sleep(500 * time.Millisecond) // 间隔避免日志混杂
}
}
关键设计说明
-
完整执行流:
- 生成唯一追踪 ID
- 设置全局超时(1500ms)
- 并发调用两个服务(随机耗时 0-2000ms/0-3000ms)
- 处理成功/失败结果
-
容错机制:
case <-ctx.Done():
results <- Result{Err: fmt.Errorf("服务取消: %v", ctx.Err())}
任何服务失败时通过cancel()
触发其他服务的快速终止
-
实战验证: 运行后会看到三种典型场景:
- 两个服务都在超时前完成(成功)
- 一个服务超时导致另一个被取消(部分失败)
- 两个服务都超时(完全失败)
-
扩展建议:
- 添加
pprof
监控查看 goroutine 泄漏 - 集成
opentelemetry
替换简单 traceID - 增加重试逻辑(需使用
context.Deadline()
剩余时间)
- 添加
5. 注意事项
- 避免滥用
WithValue
:数据传递应仅用于请求范围的元数据(如认证信息),而非函数参数。 - 及时调用
cancel()
:防止上下文泄漏(如未调用cancel
可能导致 Goroutine 堆积)。
6. 扩展学习
- 官方文档:pkg.go.dev/context
- 最佳实践:在中间件、gRPC 等框架中观察
ctx
的实际使用。