# Go Context 学习笔记
# 1. Context 接口的四个核心方法
# 1.1 接口定义
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
1
2
3
4
5
6
2
3
4
5
6
# 1.2 方法详解
# Deadline() (deadline time.Time, ok bool)
作用:返回上下文的截止时间
- 返回值:
deadline:截止时间ok:布尔值,表示是否设置了截止时间
- 含义:告诉调用者这个上下文何时应该被取消。如果没有设置截止时间,
ok返回false - 特点:连续调用会返回相同的结果
# Done() <-chan struct{}
作用:返回一个只读通道,当上下文应该被取消时,该通道会被关闭
- 返回值:只读的
struct{}类型通道 - 含义:
- 用于监听取消信号
- 通道关闭表示工作应该停止
- 如果上下文永远不会被取消,可能返回
nil
- 使用场景:主要用在
select语句中进行取消检测 - 触发条件:
WithCancel调用 cancel 函数时WithDeadline到达截止时间时WithTimeout超时时
# Err() error
作用:返回上下文被取消的原因
- 返回值:
- 如果
Done通道未关闭:返回nil - 如果
Done通道已关闭:返回具体的错误原因
- 如果
- 可能的错误类型:
DeadlineExceeded:超过截止时间Canceled:被其他原因取消
- 特点:一旦返回非
nil错误,后续调用返回相同错误
# Value(key interface{}) interface{}
作用:根据键获取上下文中存储的值
- 参数:
key- 任何支持相等比较的类型 - 返回值:与键关联的值,如果不存在则返回
nil - 使用原则:
- 仅用于请求范围的数据传递
- 不应用于传递可选参数给函数
- 键应该定义为未导出类型以避免冲突
- 特点:相同键的连续调用返回相同结果
# 2. 自定义 Context 实现示例
# 2.1 基础 Context 实现
package main
import (
"fmt"
"sync"
"time"
)
// 基础的 emptyCtx
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 2.2 可取消 Context
type cancelCtx struct {
Context
mu sync.Mutex
done chan struct{}
children map[canceler]struct{}
err error
}
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // 已经被取消了
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 2.3 超时 Context
type timerCtx struct {
cancelCtx
timer *time.Timer
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded)
return c, func() { c.cancel(false, Canceled) }
}
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
return c, func() { c.cancel(true, Canceled) }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 3. Context 使用原则
# 3.1 ✅ 应该放在 Context 中的
- 请求范围的数据:用户ID、请求ID、trace信息
- 跨进程/API边界的数据:认证信息、元数据
- 取消信号和超时:deadline、cancellation
- 调试信息:trace ID、span ID
# 3.2 ❌ 不应该放在 Context 中的
- 可选的函数参数:配置项、选项
- 业务逻辑:计算方法、验证逻辑
- 依赖项:数据库连接、HTTP 客户端
- 可变状态:计数器、累加器
# 3.3 正确的封装方式
# 请求上下文封装
// RequestContext 封装请求相关信息
type RequestContext struct {
context.Context
RequestID string
UserID int64
UserRole string
ClientIP string
UserAgent string
StartTime time.Time
TraceID string
SpanID string
}
type contextKey string
const requestContextKey contextKey = "request_context"
func NewRequestContext(ctx context.Context, c *gin.Context) *RequestContext {
reqCtx := &RequestContext{
Context: ctx,
RequestID: c.GetHeader("X-Request-ID"),
ClientIP: c.ClientIP(),
UserAgent: c.GetHeader("User-Agent"),
StartTime: time.Now(),
TraceID: c.GetHeader("X-Trace-ID"),
}
if userID, exists := c.Get("user_id"); exists {
reqCtx.UserID = userID.(int64)
}
if role, exists := c.Get("user_role"); exists {
reqCtx.UserRole = role.(string)
}
return reqCtx
}
func WithRequestContext(ctx context.Context, reqCtx *RequestContext) context.Context {
return context.WithValue(ctx, requestContextKey, reqCtx)
}
func FromContext(ctx context.Context) (*RequestContext, bool) {
reqCtx, ok := ctx.Value(requestContextKey).(*RequestContext)
return reqCtx, ok
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 认证信息封装
// 只存储认证后的数据
type AuthInfo struct {
UserID int64
SessionID string
Token string
ExpiresAt time.Time
}
type authInfoKey struct{}
func WithAuthInfo(ctx context.Context, auth *AuthInfo) context.Context {
return context.WithValue(ctx, authInfoKey{}, auth)
}
func GetAuthInfo(ctx context.Context) (*AuthInfo, bool) {
auth, ok := ctx.Value(authInfoKey{}).(*AuthInfo)
return auth, ok
}
// 专门的认证服务处理业务逻辑
type AuthService struct {
sessionStore SessionStore
tokenParser TokenParser
}
func (a *AuthService) ValidateToken(tokenString string) (*AuthInfo, error) {
token, err := a.tokenParser.Parse(tokenString)
if err != nil {
return nil, fmt.Errorf("invalid token: %w", err)
}
session, err := a.sessionStore.Get(token.SessionID)
if err != nil {
return nil, fmt.Errorf("session not found: %w", err)
}
return &AuthInfo{
UserID: session.UserID,
SessionID: session.ID,
Token: tokenString,
ExpiresAt: token.ExpiresAt,
}, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 4. 避免 Context 滥用
# 4.1 滥用示例分析
# ❌ 错误的 Token Context 设计
// 这样的设计有问题
type tokenContext struct {
session Session // ✅ 数据存储 - 合适
tokenCache Token // ✅ 数据存储 - 合适
}
// 但这样使用就有问题了:
ctx, cancel := WithTokenContext(ctx *gin.Context) // ❌ 让 Context 承担验证职责
// 验证失败时 <-ctx.Done() // ❌ 滥用取消机制
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
问题:
- 混淆了 Context 的职责:让 Context 承担验证职责
- 滥用取消机制:用取消表示业务验证失败
- 语义混乱:cancel() 的含义不明确
# ✅ 正确的设计
// Context 只存储数据 + 专门的认证服务
func AuthMiddleware(authService *AuthService) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "missing token"})
c.Abort()
return
}
// 验证 token(业务逻辑)
authInfo, err := authService.ValidateToken(token)
if err != nil {
c.JSON(401, gin.H{"error": err.Error()})
c.Abort()
return
}
// 将认证信息存储到 Context(数据传递)
ctx := WithAuthInfo(c.Request.Context(), authInfo)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4.2 依赖注入 vs Context
# ❌ 不应该通过 Context 传递的
// 错误:通过 Context 传递依赖
WithCache(ctx, redis, prefix, ttl)
WithDatabase(ctx, db)
WithLogger(ctx, logger)
1
2
3
4
2
3
4
# ✅ 应该通过依赖注入
// 正确:通过构造函数注入依赖
type AccountService struct {
emailRepo account.EmailRepository
logger *logrus.Logger
cache *redis.Client
permService *PermissionService
}
func (s *AccountService) EmailRegister(ctx context.Context, email string) error {
// 从 context 获取请求信息
reqInfo, _ := GetRequestInfo(ctx)
// 使用注入的依赖
s.logger.WithField("request_id", reqInfo.RequestID).Info("开始注册")
return s.emailRepo.Create(ctx, email)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5. 开源项目中的 Context 使用案例
# 5.1 分布式追踪 (OpenTelemetry)
// github.com/open-telemetry/opentelemetry-go
import (
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel"
)
func handleRequest(ctx context.Context) {
// 创建 span 并存储到 context
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "handle-request")
defer span.End()
// 调用其他服务时传递 trace context
processOrder(ctx)
}
func processOrder(ctx context.Context) {
// 从 context 获取当前 span
span := trace.SpanFromContext(ctx)
span.SetAttributes(attribute.String("order.id", "12345"))
// 创建子 span
_, childSpan := tracer.Start(ctx, "validate-order")
defer childSpan.End()
}
// github.com/jaegertracing/jaeger-client-go
import "github.com/opentracing/opentracing-go"
func businessLogic(ctx context.Context) {
// 从 context 获取 span
span, ctx := opentracing.StartSpanFromContext(ctx, "business-logic")
defer span.Finish()
span.SetTag("user.id", getUserID(ctx))
// 传递给下游服务
callExternalAPI(ctx)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 5.2 认证和授权信息
// google.golang.org/grpc
import (
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/credentials"
)
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
// 从 context 获取认证信息
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
token := md.Get("authorization")[0]
userInfo, err := validateToken(token)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
// 将用户信息存储到 context 传递给下游
ctx = context.WithValue(ctx, "user_info", userInfo)
return s.userService.GetUser(ctx, req.UserId)
}
// github.com/casbin/casbin
type contextKey string
const (
subjectKey contextKey = "subject"
domainKey contextKey = "domain"
)
func WithSubject(ctx context.Context, subject string) context.Context {
return context.WithValue(ctx, subjectKey, subject)
}
func WithDomain(ctx context.Context, domain string) context.Context {
return context.WithValue(ctx, domainKey, domain)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 5.3 多租户 (Multi-tenancy)
// k8s.io/apiserver/pkg/endpoints/request
type RequestInfo struct {
IsResourceRequest bool
Path string
Verb string
APIPrefix string
APIGroup string
APIVersion string
Namespace string // 租户隔离
Resource string
Subresource string
Name string
Parts []string
}
func WithRequestInfo(ctx context.Context, info *RequestInfo) context.Context {
return context.WithValue(ctx, requestInfoKey, info)
}
// istio.io/istio/pkg/config/context
type Context struct {
// 租户信息
Namespace string
Cluster string
Network string
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 5.4 请求 ID 和关联 ID
// github.com/gin-gonic/gin
func RequestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = generateRequestID()
}
// 存储到 context
ctx := context.WithValue(c.Request.Context(), "request_id", requestID)
c.Request = c.Request.WithContext(ctx)
// 添加到响应头
c.Header("X-Request-ID", requestID)
c.Next()
}
}
// github.com/labstack/echo
func (e *Echo) Use(middleware ...MiddlewareFunc) {
// Echo 内部使用 context 存储请求相关信息
c.Set("request_id", requestID)
c.Set("user_id", userID)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5.5 地理位置和语言
// Shopify 风格的地理位置信息
type LocationContext struct {
Country string
Region string
Currency string
Language string
Timezone string
}
func WithLocation(ctx context.Context, loc *LocationContext) context.Context {
return context.WithValue(ctx, locationKey, loc)
}
// github.com/stripe/stripe-go
type RequestContext struct {
Account string // Stripe Connect 账户
APIKey string // API 密钥
UserAgent string
Language string // 国际化
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5.6 工作流和状态信息
// go.temporal.io/sdk/workflow
func MyWorkflow(ctx workflow.Context, input string) error {
// workflow context 包含执行信息
info := workflow.GetInfo(ctx)
logger := workflow.GetLogger(ctx)
logger.Info("Workflow started", "WorkflowID", info.WorkflowExecution.ID)
// 传递给 activity
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: time.Minute,
})
return workflow.ExecuteActivity(ctx, MyActivity, input).Get(ctx, nil)
}
// go.uber.org/cadence/workflow
type WorkflowContext struct {
WorkflowID string
RunID string
TaskList string
Domain string
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5.7 组织和部门信息
// gitlab.com/gitlab-org/gitlab
type ProjectContext struct {
ProjectID int64
NamespaceID int64
UserID int64
GroupID int64 // 组织信息
}
func WithProjectContext(ctx context.Context, pc *ProjectContext) context.Context {
return context.WithValue(ctx, projectContextKey, pc)
}
// github.com/google/go-github
type Context struct {
Owner string // 组织或用户
Repository string // 仓库
Branch string // 分支
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 5.8 设备和客户端信息
// firebase.google.com/go
type ClientContext struct {
AppID string
Platform string // iOS, Android, Web
Version string
DeviceID string
UserAgent string
}
// github.com/twilio/twilio-go
type RequestContext struct {
AccountSid string
AuthToken string
Region string
Edge string
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 5.9 监控和指标
// github.com/prometheus/client_golang
import "github.com/prometheus/client_golang/prometheus"
type MetricsContext struct {
Labels prometheus.Labels
Registry *prometheus.Registry
}
func WithMetrics(ctx context.Context, mc *MetricsContext) context.Context {
return context.WithValue(ctx, metricsKey, mc)
}
// github.com/DataDog/dd-trace-go
import "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
func handleRequest(ctx context.Context) {
span, ctx := tracer.StartSpanFromContext(ctx, "web.request")
defer span.Finish()
span.SetTag("user.id", getUserID(ctx))
span.SetTag("http.method", "POST")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 6. 适合存储在 Context 中的数据特征
# 6.1 核心特征
- 请求范围:与特定请求相关
- 只读数据:不会被修改
- 跨层传递:需要在多个层级间传递
- 身份标识:用户、租户、会话等身份信息
- 元数据:请求ID、追踪信息、地理位置等
# 6.2 常见使用场景
- 用户身份:UserID, SessionID, Role
- 请求追踪:RequestID, TraceID, SpanID
- 多租户:TenantID, Namespace, Organization
- 地理位置:Country, Region, Timezone, Language
- 设备信息:Platform, Version, DeviceID
- 工作流:WorkflowID, ExecutionID, TaskID
# 7. 学习资源
# 7.1 官方资源
# 7.2 开源项目示例
# OpenTelemetry 分布式追踪
git clone https://github.com/open-telemetry/opentelemetry-go
cd opentelemetry-go/example
# gRPC 认证示例
git clone https://github.com/grpc/grpc-go
cd grpc-go/examples/features/authentication
# Gin 中间件示例
git clone https://github.com/gin-gonic/examples
cd examples/middleware
# Kubernetes 请求处理
git clone https://github.com/kubernetes/kubernetes
# 查看 staging/src/k8s.io/apiserver/pkg/endpoints/request/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8. 总结
Context 是 Go 语言中强大的上下文管理机制,正确使用的关键是:
- 明确职责:Context 用于数据传递,不承担业务逻辑
- 遵循原则:只存储请求范围的只读数据
- 避免滥用:不要用 Context 替代依赖注入
- 学习实践:参考优秀开源项目的使用方式
- 类型安全:使用自定义 key 类型避免冲突
通过合理使用 Context,可以构建出清晰、可维护、可测试的 Go 应用程序。