面试题答案
一键面试具体场景
假设我们的微服务系统中有一个订单处理服务,该服务需要调用多个下游服务来完成订单的创建、支付处理以及库存更新等操作。同时,由于高并发,可能会出现用户取消订单的情况,我们需要能够及时通知各个下游服务停止处理。
使用context辅助函数的方式
- 创建上下文:使用
context.Background()
创建一个根上下文。在订单处理函数的入口处,基于根上下文创建一个可取消的上下文,例如:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- 传递上下文:将创建好的上下文
ctx
传递给所有调用的下游服务函数。比如,在调用支付服务函数ProcessPayment(ctx, order)
时,将上下文作为参数传入,这样支付服务就可以感知到外部的取消信号。 - 取消操作:如果用户取消订单,调用
cancel()
函数,这会向所有基于该上下文衍生的子上下文发送取消信号。下游服务在处理过程中,可以通过ctx.Done()
通道来检测是否收到取消信号。例如在支付服务函数内:
func ProcessPayment(ctx context.Context, order Order) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 正常支付处理逻辑
}
return nil
}
不同辅助函数性能表现差异
- context.WithCancel:性能较好,它主要用于创建一个可取消的上下文,开销较小,适用于需要随时取消操作的场景,如上述订单处理中用户取消订单的情况。
- context.WithTimeout:会在指定的时间后自动取消上下文。性能上,由于需要额外维护一个定时器,相比
context.WithCancel
会有稍高的开销。在订单处理场景中,如果我们对每个下游服务的处理设置一个超时时间,比如支付处理必须在30秒内完成,就可以使用context.WithTimeout
。但如果不必要地设置超时,频繁的定时器操作会对性能有一定影响。 - context.WithDeadline:与
context.WithTimeout
类似,不过是基于一个绝对的截止时间。性能开销同样在于维护截止时间的检查逻辑。如果订单处理需要在某个绝对时间点前完成,就可以使用context.WithDeadline
。但同样,如果设置不合理,频繁检查截止时间会影响性能。
性能优化
- 合理选择辅助函数:根据实际需求准确选择
context
辅助函数。如果仅需手动取消,优先使用context.WithCancel
;若有时间限制,根据是相对时间(超时)还是绝对时间(截止日期)选择context.WithTimeout
或context.WithDeadline
,避免过度使用定时器相关的辅助函数造成不必要的开销。 - 及时取消上下文:在确定不再需要某个操作时,尽早调用取消函数,避免不必要的计算和资源占用。例如在订单取消时,尽快调用
cancel()
,使得下游服务能快速响应并停止处理。 - 复用上下文:在一些情况下,如果上下文的生命周期可以复用,尽量复用已有的上下文,减少频繁创建和销毁上下文带来的开销。比如在一个长时间运行的任务中,对于一些子任务可以复用相同的上下文。