面试题答案
一键面试基于Context设计实现调用链
- 传递Context:
- 在服务A调用服务B时,创建一个Context并传递。例如:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() resultB, err := serviceB.Call(ctx) if err!= nil { // 处理错误 }
- 在服务B调用服务C时,继续传递从服务A传来的Context:
func Call(ctx context.Context) (resultC, error) { resultC, err := serviceC.Call(ctx) if err!= nil { // 处理错误 } return resultC, nil }
- 传递元数据:可以使用context.WithValue来携带元数据。例如,在服务A中:
在服务B和服务C中,可以通过ctx.Value来获取元数据:ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx = context.WithValue(ctx, "userID", "12345") defer cancel() resultB, err := serviceB.Call(ctx)
func Call(ctx context.Context) (resultC, error) { userID := ctx.Value("userID") // 使用userID resultC, err := serviceC.Call(ctx) if err!= nil { // 处理错误 } return resultC, nil }
- 故障取消后续调用:当任何一个服务出现故障时,通过取消Context来通知后续服务。例如,在服务B中出现错误时:
func Call(ctx context.Context) (resultB, error) { var resultB ResultB var err error // 假设这里出现错误 err = someError() if err!= nil { cancel := ctx.Value(cancelKey) if cancel!= nil { cancel() } return resultB, err } resultC, err := serviceC.Call(ctx) if err!= nil { // 处理错误 } // 处理resultC得到resultB return resultB, nil }
可能遇到的问题及解决方案
- Context滥用:
- 问题:过度使用context.WithValue传递大量数据,导致代码可读性变差,且难以追踪数据来源。
- 解决方案:只传递必要的跨服务元数据,其他数据通过函数参数传递。
- 超时设置不合理:
- 问题:如果超时时间设置过短,可能导致正常请求被误判为失败;设置过长,可能影响系统整体性能。
- 解决方案:通过性能测试和实际业务场景分析,合理设置超时时间,并根据运行时情况动态调整。
- 取消不及时:
- 问题:在某些复杂业务逻辑中,可能无法及时检测到上游服务的取消信号,导致资源浪费。
- 解决方案:在关键业务逻辑点定期检查Context的Done通道,及时响应取消信号。例如:
for { select { case <-ctx.Done(): return nil, ctx.Err() default: // 执行正常业务逻辑 } }