手搓 RPC 框架系列(一):基于架构设计原则的 RPC 基础架构设计

文 / Kenyon,资深软件架构师,15 年软件开发和技术管理经验,从程序员做到企业技术高管,专注技术管理、架构设计、AI 技术应用和落地。
由于公众号推流的原因,请在关注页右上角加星标,这样才能及时收到新文章的推送。
引言
在上两篇文章中,我们深入探讨了架构设计的核心原则、方法和模式,从 SOLID 基本设计原则到分布式系统的 CAP 定理,这些都是构建高质量系统的基础。今天,我们将把这些理论知识应用到实践中,让我带大家从零开始构建一个类似 Dubbo 的 RPC 框架。
RPC(Remote Procedure Call,远程过程调用)是分布式系统中服务之间通信最核心的机制,它允许一个进程去调用一个可以是远在天边的另外一个进程中的函数,虽然相隔甚远,但是调用的时候就像调用本地函数一样简单。Dubbo 作为国内最流行的 RPC 框架之一,它的设计理念和架构的实现充分体现了我们之前学习的各种架构设计原则。
一、核心需求分析
在开始设计之前,我们先明确一下我们的这个 RPC 框架的核心需求,根据 YAGNI 原则的要求,我们只实现当前必要的功能,主要是下面的 5 点:
具备远程调用的能力:允许客户端像调用本地方法一样调用远程的服务实例
可以进行服务注册与发现:客户端能够自动发现可供调用的服务实例,无需手动去配置
具备负载均衡的能力:客户端或者服务端可以根据负载均衡的策略将请求分发到多个实例上面,提高系统可用性和性能
序列化与反序列化:客户端可以将对象转换为可以进行网络传输的格式,服务端再把对应的格式转换为对象,并且支持跨语言调用
可以利用网络进行通信:处理底层网络传输,支持不同的协议(如 TCP、HTTP)
二、基于架构设计原则的 RPC 架构设计
1. 基于 SOLID 原则的核心组件设计
单一职责原则(SRP)
因为远程调用是 RPC 框架的核心功能,所以我们将 RPC 框架拆分为多个职责单一的组件:
Proxy 层:主要是负责将客户端的本地方法调用转换为远程调用
Registry 层:主要负责服务的注册与发现,服务端可以在上面注册自己的服务实例,客户端可以通过注册中心发现可用的服务实例
LoadBalance 层:主要负责服务调用时的负载均衡策略,将客户端的请求分发到多个服务实例上面,提高系统的可用性和性能
Serializer 层:主要负责序列化与反序列化,将对象转换为网络传输格式,支持跨语言调用
Transport 层:负责网络通信,处理底层的网络传输,支持不同的协议(如 TCP、HTTP 等)
开闭原则(OCP)
通过抽象接口实现组件的可扩展性:
依赖倒置原则(DIP)
高层的模块都不依赖底层模块的具体实现,全都依赖其抽象类或接口:
比如我们在实现 RPC 框架核心模块的逻辑的时候,都是依赖 Serializer、LoadBalancer、RegistryCenter 等抽象接口
像具体的实现类(如 JsonSerializer、RandomLoadBalancer)都是依赖上面定义的这些接口
2. 基于通用设计原则的架构优化
高内聚低耦合
每个组件内部的功能和实现逻辑都是紧密相关的(高内聚)
组件之间通过接口来进行通信,尽量减少直接的依赖(低耦合)
例如:Proxy 层不可以直接依赖具体的 Transport 的实现,而是通过 Transport 的接口来进行通信
KISS 原则(Keep It Simple, Stupid)
初始的 MVP 版本只需要实现核心的功能就可以了,避免出现过度设计的情况
接口的设计要简洁明了,减少不必要的参数和复杂的逻辑
例如:服务注册只需要服务名称、地址和端口即可,客户端只需要知道服务名称即可调用
迪米特法则
组件之间只与直接依赖的组件通信
例如:客户端不需要知道服务注册中心的具体实现,只需要通过 Registry 接口调用即可
三、RPC 框架的核心架构图
基于以上设计原则,我们可以画出 RPC 框架的核心架构图:
四、核心组件的详细设计
1. 服务代理(Service Proxy)
该组件负责将客户端的本地方法调用转换为远程调用
实现方式:JDK 动态代理、CGLIB 代理
核心功能:
拦截方法调用
构建请求对象
选择服务实例
发送远程请求
处理响应结果
2. 服务注册中心(Registry Center)
该组件负责客户端和服务端的服务注册与发现
实现方式:可以实现基于 ZooKeeper、Nacos 等注册中心来实现服务的注册与发现
核心功能:
服务注册:服务端将服务实例的信息注册到注册中心上面,主要的信息包括服务名称、地址和端口等信息
服务发现:客户端从注册中心获取服务实例的列表,根据服务名称来选择需要调用的服务实例
服务监听:监听服务实例的上下线变化,及时更新本地的服务实例列表,以免调用到已经下线的服务实例
3. 负载均衡(Load Balance)
该组件负责将客户端的请求分发到服务端的实例上面进行处理,如果有多个实例的话,就可以根据指定的负载均衡算法来选择其中一个实例进行处理
常见算法:
随机算法(Random),在多个实例中随机选择一个进行处理
轮询算法(Round Robin),按照服务端在注册中心注册时的顺序依次选择实例进行处理
一致性哈希算法(Consistent Hash),根据请求的哈希值来选择实例进行处理,能够保持请求的一致性
加权轮询算法(Weighted Round Robin),根据实例的权重来选择实例进行处理,权重高的实例会被更多的请求处理
4. 序列化(Serializer)
该组件负责将请求和处理结果的对象转换成可以支持网络传输的格式
常用序列化方式:
JSON:可读性好,支持跨语言处理
Protobuf:性能高,空间效率好
Hessian:Java 原生支持,性能还算较好
5. 网络传输(Transport)
该组件负责整个 RPC 框架底层的网络通信
实现方式:
Netty:一个基于异步事件驱动的高性能网络通信框架
Bio:同步阻塞 IO,一般不建议使用
Nio:同步非阻塞 IO,性能较高,但是实现复杂
五、总结与下一步计划
在上文中,我们基于 SOLID 原则和通用设计原则,设计了一个类似 Dubbo 的 RPC 框架的基础架构。我们将 RPC 框架拆分为多个职责单一的组件,然后可以通过抽象接口的实现来扩展和替换这些组件,实现组件的可扩展性,并遵循了高内聚低耦合、KISS 等设计原则。
在下一篇文章中,我们将进入实战阶段,实现 RPC 框架的核心功能,包括服务代理、序列化、网络通信等模块。
互动话题:你对 RPC 框架的设计有什么看法?你认为 RPC 框架中最重要的组件是什么?欢迎在评论区分享你的观点。
关于作者
Kenyon,资深软件架构师,15 年的软件开发和技术管理经验,从程序员做到企业技术高管。多年企业数字化转型和软件架构设计经验,善于帮助企业构建高质量、可维护的软件系统,目前专注技术管理、架构设计、AI 技术应用和落地;全网统一名称"六边形架构",欢迎关注交流。
原创不易,转载请联系授权,如果觉得有帮助,请点赞、收藏、转发三连支持!
版权声明: 本文为 InfoQ 作者【六边形架构】的原创文章。
原文链接:【http://xie.infoq.cn/article/f97848bb869f15e56a683b04c】。文章转载请联系作者。







评论