设计模式和架构层面
- Context传递原则:
- 自上而下传递:在整个调用链中,Context应从入口处开始向下传递到每一个需要的函数调用。例如,在HTTP服务的入口处,创建一个带有必要超时或取消功能的Context,并将其传递到处理请求的各个微服务函数中。
- 避免Context泄露:确保在不再需要Context时,它能够被正确地取消或超时,以防止资源泄漏。
- 链路追踪:
- 使用分布式追踪系统:如Jaeger、Zipkin等。在创建Context时,可以将追踪相关的信息(如TraceID、SpanID)放入Context的Value中,这样在整个调用链中传递Context时,这些信息也能随之传递,便于追踪系统记录和关联各个请求。例如:
func handler(w http.ResponseWriter, r *http.Request) {
span, ctx := tracer.Start(r.Context(), "handler")
defer span.End()
// 将追踪信息放入Context
ctx = context.WithValue(ctx, "traceID", span.Context().TraceID().String())
// 传递Context进行后续处理
processRequest(ctx)
}
- 超时控制:
- 设置合理的超时时间:在发起请求的地方,根据业务需求设置合适的超时时间。例如,在调用外部API时,可以创建一个带有超时的Context:
func callExternalAPI(ctx context.Context) error {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// 发起API调用,传入带有超时的Context
// 假设这里有一个模拟的API调用函数
err := apiCall(ctx)
return err
}
- 资源清理:
- 利用Context取消信号:在子请求或资源获取的函数中,监听Context的取消信号。当Context被取消(如超时或手动取消)时,及时清理相关资源。例如,在获取数据库连接或打开文件时:
func readFile(ctx context.Context, filePath string) ([]byte, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
// 创建一个新的Context,用于在取消时关闭文件
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// 使用带有取消功能的Context进行文件读取操作
// 假设这里有一个模拟的文件读取函数
data, err := readFileWithCtx(ctx, file)
return data, err
}
示例代码
- 简单的HTTP服务示例:
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func processRequest(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("Request cancelled or timed out")
case <-time.After(2 * time.Second):
fmt.Println("Request processed successfully")
}
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
processRequest(ctx)
w.Write([]byte("Request processed"))
})
fmt.Println("Server listening on :8080")
http.ListenAndServe(":8080", nil)
}
- 微服务间调用示例:
package main
import (
"context"
"fmt"
"time"
)
func service1(ctx context.Context) error {
fmt.Println("Service1 started")
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
err := service2(ctx)
if err != nil {
fmt.Println("Service2 failed:", err)
}
fmt.Println("Service1 ended")
return err
}
func service2(ctx context.Context) error {
fmt.Println("Service2 started")
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(1 * time.Second):
fmt.Println("Service2 completed")
return nil
}
}
func main() {
ctx := context.Background()
err := service1(ctx)
if err != nil {
fmt.Println("Overall service failed:", err)
}
}