使用Context设计与实现
- 创建顶层Context:在调用微服务的入口处,创建一个带有取消功能的顶层
Context
,例如使用context.WithCancel
或context.WithTimeout
。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
- 传递Context:将这个
Context
传递给每个在Goroutine中执行的下游服务调用函数。
var wg sync.WaitGroup
for _, service := range downstreamServices {
wg.Add(1)
go func(svc Service) {
defer wg.Done()
err := svc.Call(ctx)
if err != nil {
// 处理错误
}
}(service)
}
- 在下游服务调用中检查Context:在每个下游服务调用函数内部,定期检查
Context
的取消信号。例如,在进行HTTP请求时,可以将Context
传递给HTTP客户端。
func (s *Service) Call(ctx context.Context) error {
req, err := http.NewRequestWithContext(ctx, "GET", s.url, nil)
if err != nil {
return err
}
resp, err := s.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// 处理响应
return nil
}
- 超时处理:如果顶层
Context
设置了超时,当超时发生时,所有传递了该Context
的Goroutine会收到取消信号,它们在检查Context
时会提前结束操作。
可能遇到的问题及解决方案
- 资源清理:
- 问题:Goroutine在收到取消信号后,可能没有正确清理资源,如文件描述符、数据库连接等。
- 解决方案:在Goroutine结束前,确保调用相应的资源清理函数。例如,对于文件操作,使用
defer file.Close()
;对于数据库连接,使用defer db.Close()
。
- 同步问题:
- 问题:当一个Goroutine超时取消后,其他Goroutine可能还在执行中,导致数据不一致或状态混乱。
- 解决方案:使用
sync.WaitGroup
来等待所有Goroutine完成,并且在取消时确保所有Goroutine都能正确响应取消信号。在Goroutine内部,在接收到取消信号后,尽快结束业务逻辑,并通过wg.Done()
通知等待组。
- 嵌套Context:
- 问题:如果在下游服务调用中又创建了新的
Context
,并且没有正确传递取消信号,可能导致子Context
不会被取消。
- 解决方案:始终使用从上层传递下来的
Context
,如果需要创建新的Context
,确保使用WithCancel
、WithTimeout
等函数基于上层Context
创建,以保证取消信号能正确传递。例如childCtx, childCancel := context.WithCancel(parentCtx)
。