字节RPC框架kitex源码阅读(一)
Note 基于kitex@v0.11.3 开篇 随着分布式系统的发展,RPC(Remote Procedure Call,远程过程调用)已成为微服务架构中不可或缺的基础组件。RPC 通过让服务之间像调用本地方法一样发起远程调用,极大简化了跨进程、跨服务器的通信复杂度。对于开发者来说,选择一个性能稳定、易于使用的 RPC 框架至关重要。 ...
Note 基于kitex@v0.11.3 开篇 随着分布式系统的发展,RPC(Remote Procedure Call,远程过程调用)已成为微服务架构中不可或缺的基础组件。RPC 通过让服务之间像调用本地方法一样发起远程调用,极大简化了跨进程、跨服务器的通信复杂度。对于开发者来说,选择一个性能稳定、易于使用的 RPC 框架至关重要。 ...
前言 本文基于go1.21,不同版本的Context内部实现可能会有细微差别 使用场景 为什么需要Context,首先思考一个场景:客户端去请求某个服务,这个服务依赖于多个可并行执行的下游服务,为了提高这个服务的响应速度,自然会为每个下游服务开启一个协程去执行。 倘若在执行的过程中,客户端取消了对这个服务的请求,下游服务也应该被停掉。但是go不提供直接关闭协程的方法,我们可以使用chan来协作式地关闭这些子协程:在服务代码中创建一个no buffer的chan并传递给这些子协程,通过子协程read chan+父亲协程close chan来达到通知子协程关闭的效果。 我们将这个场景扩展一下: 在这个场景中,child svc1又依赖于两个下游服务,并且某次请求中child svc1因为某些原因会取消对下游服务的请求(不影响其他服务节点的正常运行),老样子,在child svc1中创建一个chan并通过这个chan去关闭下游协程就好了。child svc1的代码可能会这么写: func childSvc1(upstream <-chan) { downstream := make(chan struct{}, 0) defer close(downstream) go childSvc1_1(downstream) go childSvr1_2(downstream) for { select { case <-upstream: close(downstream) return default: // 处理业务... // 因为某些原因需要取消下游服务 close(downstream) } } } 可以看到这段小snippet还能勉强阅读,但是从使用语义上来说,chan本身是用于传递数据的,这种read+close来关闭协程的方式就是某种hack手段,对于有一定规模的项目来说,这样的使用方式可读性将会非常差并且容易出错。因此我们需要一个带有“取消下游”语义的库来完成这种任务。 Context则很好地充当了这样的角色,它不但能控制下游的生命的生命周期,还自带超时控制、取消传递、并发安全、数据传递等功能: 取消传递:父Context的取消会自动传递到所有子孙Context,使得子孙Context也自动取消 超时控制:通过 context.WithTimeout 和 context.WithDeadline,提供自动超时取消机制 数据传递:可以携带请求范围内的数据(比如请求ID、path、begin timestamp等),这个特性在分布式链路追踪框架中被广泛使用 上面第二个场景中的代码换成Context来实现: import "context" func childSvc1(ctx context.Context) { cancel, ctx := context.WithCancel(ctx) // 创建子Context defer cancel() go childSvc1_1(ctx) go childSvr1_2(ctx) for { select { case ctx.Done(): return default: // 处理业务... // 因为某些原因需要取消下游服务 cancel() } } } 带超时控制的Context: ...