Netty从0到1系列之RPC通信
文章目录
- RPC技术
- 一、RPC 的核心目标
- 二、RPC核心组成部分
- 三、RPC 通信的完整流程
- 3.1 客户端调用代理对象
- 3.2 客户端 Stub 封装请求
- 3.3 网络传输层发送请求
- 3.4 服务端 Stub 解析请求
- 3.5 服务端执行方法并返回结果
- 3.6 服务端 Stub 封装响应
- 3.7 客户端接收并处理响应
- 四、RPC 的关键技术点
- 4.1 序列化 / 反序列化
- 4.2 网络传输
- 4.3 服务注册与发现
- 4.4 负载均衡
- 4.5 容错机制
- 4.6 RPC 完整的工作流程
- 五、RPC vs HTTP
- 六、主流rpc框架特点
- 七、性能优化关键技术
- 7.1 连接池管理
- 7.2 异步调用与回调
- 八、RPC 的核心价值与学习要点
推荐阅读:
【01】Netty从0到1系列之I/O模型
【02】Netty从0到1系列之NIO
【03】Netty从0到1系列之Selector
【04】Netty从0到1系列之Channel
【05】Netty从0到1系列之Buffer(上)
【06】Netty从0到1系列之Buffer(下)
【07】Netty从0到1系列之零拷贝技术
【08】Netty从0到1系列之整体架构、入门程序
【09】Netty从0到1系列之EventLoop
【10】Netty从0到1系列之EventLoopGroup
【11】Netty从0到1系列之Future
【12】Netty从0到1系列之Promise
【13】Netty从0到1系列之Netty Channel
【14】Netty从0到1系列之ChannelFuture
【15】Netty从0到1系列之CloseFuture
【16】Netty从0到1系列之Netty Handler
【17】Netty从0到1系列之Netty Pipeline【上】
【18】Netty从0到1系列之Netty Pipeline【下】
【19】Netty从0到1系列之Netty ByteBuf【上】
【20】Netty从0到1系列之Netty ByteBuf【中】
【21】Netty从0到1系列之Netty ByteBuf【下】
【22】Netty从0到1系列之Netty 逻辑架构【上】
【23】Netty从0到1系列之Netty 逻辑架构【下】
【24】Netty从0到1系列之Netty 启动细节分析
【25】Netty从0到1系列之Netty 线程模型【上】
【26】Netty从0到1系列之Netty 线程模型【下】
【27】Netty从0到1系列之Netty ChannelPipeline
【28】Netty从0到1系列之Netty ChannelHandler
【29】Netty从0到1系列之Netty拆包、粘包【1】
【30】Netty从0到1系列之Netty拆包、粘包【2】
【31】Netty从0到1系列之Netty拆包、粘包【3】
【32】Netty从0到1系列之Netty拆包、粘包【4】
【33】Netty从0到1系列之Netty拆包、粘包【5】
【34】Netty从0到1系列之动态从内存分配】
【35】Netty从0到1系列之writeAndFlush原理分析】
【36】Netty从0到1系列之Netty内存管理【上】】
【37】Netty从0到1系列之Netty内存管理【下】】
【38】Netty从0到1系列之Netty内存管理【1】】
【39】Netty从0到1系列之Netty内存管理【2】】
【40】Netty从0到1系列之Netty内存管理【3】】
【41】Netty从0到1系列之Netty内存管理【4】】
【42】Netty从0到1系列之Netty零拷贝技术】
【43】Netty从0到1系列之内置Handler【上】】
【44】Netty从0到1系列之内置Handler【下】】
【45】Netty从0到1系列之基于Netty的开发流程】
RPC技术
RPC(Remote Procedure Call,远程过程调用)是分布式系统中核心的通信技术,它允许一台计算机上的程序(客户端)像调用本地方法一样,调用另一台计算机上的程序(服务端)的方法,屏蔽了底层网络通信的复杂性。其核心价值在于简化分布式系统的开发,让开发者无需关注网络协议、数据传输等细节,专注于业务逻辑。
✅ 1. rpc架构体系
✅ 2. 动态代理机制
一、RPC 的核心目标
"像调用本地方法一样调用远程方法"
在单机程序中,方法调用是直接通过内存地址跳转实现的;而在分布式系统中,服务部署在不同机器,必须通过网络传输数据。RPC 的本质是对网络通信的封装,通过一系列机制将 “远程调用” 伪装成 “本地调用”,流程上对开发者透明。
举个例子:
- 本地调用:
int result = calculator.add(1, 2);
(直接在进程内执行) - 远程调用:同样写
int result = calculator.add(1, 2);
,但calculator
是远程服务的代理,实际通过网络调用另一台机器的add
方法。
二、RPC核心组成部分
一个完整的 RPC 框架通常包含 6 个核心模块,协同实现远程调用:
模块 | 作用 |
---|---|
客户端(Client) | 发起远程调用的一方,调用 “代理对象” 的方法。 |
服务端(Server) | 提供服务的一方,注册服务并等待客户端调用,执行方法后返回结果。 |
代理(Stub) | 客户端的 “替身”,负责将本地方法调用转化为网络请求(序列化参数、组装请求);服务端的 “替身” 负责解析请求、调用本地方法、序列化返回结果。 |
网络传输层 | 负责底层数据的发送与接收,基于 TCP/UDP/HTTP 等协议,常用 Netty、Mina 等框架实现。 |
序列化 / 反序列化 | 将内存中的对象(如方法名、参数、返回值)转化为可网络传输的字节流(序列化),或反之(反序列化)。 |
服务注册与发现 | 管理服务地址(IP: 端口),客户端通过服务名查询可用的服务端地址(如 ZooKeeper、Nacos)。 |
三、RPC 通信的完整流程
以 " 客户端调用服务端
add
方法 " 为例
3.1 客户端调用代理对象
客户端通过calculator.add(1, 2)
调用的是代理对象(由 RPC 框架自动生成),而非真实的服务端对象。
3.2 客户端 Stub 封装请求
代理对象(客户端 Stub)执行以下操作:
- 收集调用信息:方法名(
add
)、参数(1, 2
)、参数类型等。 - 序列化:将这些信息转化为字节流(使用 Protobuf、JSON 等协议)。
- 组装请求:按 RPC 协议(如自定义协议、gRPC 的 HTTP/2)包装成 “请求包”(含服务名、请求 ID 等元数据)。
3.3 网络传输层发送请求
客户端的网络模块(如 Netty 的 Channel)通过 TCP 连接将请求包发送到服务端。
3.4 服务端 Stub 解析请求
服务端的网络模块接收请求后,交给服务端 Stub 处理:
- 反序列化:将字节流还原为方法名、参数等对象。
- 定位服务:根据服务名找到对应的本地实现类(如CalculatorImpl)。
3.5 服务端执行方法并返回结果
服务端 Stub 调用本地方法(CalculatorImpl.add(1, 2)),得到结果(3
)。
3.6 服务端 Stub 封装响应
服务端 Stub 将返回结果序列化,按协议组装成 “响应包”(含请求 ID、结果数据),通过网络传输层发回客户端。
3.7 客户端接收并处理响应
客户端网络模块接收响应包,交给客户端 Stub:
- 反序列化:将字节流还原为返回值(
3
)。 - 回调客户端:将结果返回给客户端的调用处(
int result = ...
)。
整个过程对开发者完全透明,感知不到网络交互的存在。
四、RPC 的关键技术点
4.1 序列化 / 反序列化
数据格式的 "翻译官"
- 核心需求:高效(小体积、快速度)、兼容(跨语言 / 版本)、安全(防注入)。
- 常见方案
- 二进制协议:Protobuf(谷歌,高效、跨语言)、Thrift(Facebook,支持多协议)、Hessian(Java 生态,兼容性好)。
- 文本协议:JSON(简单、可读性强,但体积大)、XML(冗余度高,逐渐被淘汰)。
- 选择原则:高性能场景选二进制(如 Protobuf),跨语言且对性能要求不高选 JSON。
序列化与反序列化组件:
协议 | 性能 | 可读性 | 跨语言 | 适用场景 |
---|---|---|---|---|
JSON | 中 | 好 | 好 | HTTP API, Web应用 |
Protobuf | 高 | 差 | 好 | 高性能RPC, 微服务 |
Thrift | 高 | 中 | 好 | 跨语言服务 |
Avro | 高 | 差 | 好 | 大数据场景 |
Hessian | 中 | 差 | 好 | Java间通信 |
序列化流程:
4.2 网络传输
数据的 “运输通道”
-
- TCP:可靠传输(三次握手、重传机制),适合需要确保数据完整性的场景(如金融交易),是 RPC 的主流选择(Netty 基于 TCP)。
- UDP:不可靠但速度快,适合实时性要求高的场景(如游戏同步),少数 RPC 框架支持(如自定义 UDP 协议)。
- HTTP/2:基于 TCP,支持多路复用、头部压缩,gRPC 采用此协议,兼顾兼容性和性能。
- TCP:可靠传输(三次握手、重传机制),适合需要确保数据完整性的场景(如金融交易),是 RPC 的主流选择(Netty 基于 TCP)。
- 连接方式
- 长连接:客户端与服务端建立一次连接后复用(减少握手开销),适合高频调用(几乎所有 RPC 框架默认采用)。
- 短连接:每次调用建立新连接(开销大),仅用于低频场景。
4.3 服务注册与发现
服务器的地址簿
- 核心问题:分布式系统中服务端可能动态扩缩容(IP: 端口变化),客户端如何找到可用服务?
- 流程
- 服务端启动时,将自己的地址(IP: 端口)和服务名注册到注册中心(如 ZooKeeper)。
- 客户端启动时,从注册中心订阅服务名对应的地址列表,并缓存到本地。
- 服务端下线时,注册中心触发通知,客户端更新本地地址列表。
- 常见注册中心:ZooKeeper(强一致性,适合高可靠场景)、Nacos(支持 AP/CP 模式,易用性好)、Eureka(Netflix,AP 模式,适合云原生)。
服务注册与发现中心
4.4 负载均衡
请求的 “调度员”
- 核心目标:将客户端请求均匀分配到多个服务端实例,避免单节点过载。
- 常见策略
- 轮询(Round Robin):按顺序依次分配(简单,适合服务能力相近的场景)。
- 权重(Weighted):给性能好的服务端分配更高权重(如配置权重值 3:1)。
- 一致性哈希(Consistent Hashing):相同参数的请求路由到同一服务端(适合有本地缓存的场景)。
- 最小活跃数(Least Active):优先分配给处理请求最少的服务端(动态适应负载)。
4.5 容错机制
系统的 “安全阀”
分布式环境中网络波动、服务宕机不可避免,RPC 需要容错机制保证稳定性:
- 超时重试:请求超时后自动重试(需确保方法幂等性,避免重复执行导致数据错误)。
- 熔断:当服务端错误率超过阈值时,暂时停止调用(如 10 秒内 50% 请求失败,熔断 30 秒),避免雪崩效应(Netflix Hystrix、Sentinel)。
- 降级:服务不可用时,返回默认值(如库存查询失败时返回 “暂时无法查询”)。
- 负载均衡容错:检测到服务端不可达时,自动切换到其他实例(如 Dubbo 的 “失败自动切换”)。
4.6 RPC 完整的工作流程
五、RPC vs HTTP
为什么不直接用 HTTP 接口?
很多人会疑惑:“HTTP 也能实现远程调用,为什么需要 RPC?” 两者的核心区别在于设计目标
维度 | RPC | HTTP(如 RESTful) |
---|---|---|
定位 | 专为分布式服务调用设计,追求效率和易用性 | 通用协议,适合跨系统(如前端 - 后端)交互 |
协议复杂度 | 自定义协议(紧凑,如仅包含方法名 + 参数) | 标准 HTTP 协议(头部冗余,如 Cookie、Host 等) |
性能 | 更高(二进制序列化 + 长连接,减少开销) | 较低(文本序列化 + 头部开销) |
易用性 | 客户端直接调用方法,无需拼接 URL 和参数 | 需要手动处理 URL、请求体、响应解析 |
服务治理 | 内置服务发现、负载均衡、容错等 | 需额外集成(如 Spring Cloud+Eureka) |
结论:内部服务间高频调用用 RPC(如微服务架构),跨系统、低频次调用用 HTTP(如开放 API)。
六、主流rpc框架特点
框架 | 特点 | 适用场景 |
---|---|---|
gRPC | 谷歌开源,基于 HTTP/2 和 Protobuf,跨语言支持好(Java、Go、Python 等),性能优异。 | 跨语言服务调用、高性能场景 |
Dubbo | 阿里开源,Java 生态为主,功能丰富(服务发现、负载均衡、熔断等),国内企业广泛使用。 | Java 微服务架构 |
Thrift | Facebook 开源,支持多语言、多协议(TCP、HTTP),序列化效率高。 | 跨语言、高性能数据传输 |
Spring Cloud OpenFeign | 基于 HTTP 的声明式调用,整合 Spring 生态,易用性强。 | Java 生态,偏好 HTTP 协议的场景 |
七、性能优化关键技术
7.1 连接池管理
public class ConnectionPool {private final Map<String, List<Channel>> pool = new ConcurrentHashMap<>();private final int maxConnectionsPerAddress;public Channel getConnection(String address) {return pool.compute(address, (addr, channels) -> {if (channels == null) {channels = new ArrayList<>();}// 清理无效连接channels.removeIf(channel -> !channel.isActive());// 创建新连接if (channels.size() < maxConnectionsPerAddress) {Channel newChannel = createChannel(addr);channels.add(newChannel);return newChannel;}// 使用负载均衡策略选择连接return selectChannel(channels);});}
}
7.2 异步调用与回调
public class AsyncRpcCall {public CompletableFuture<User> getUserAsync(Long id) {RpcRequest request = buildRequest(id);RpcFuture<RpcResponse> future = new RpcFuture<>();// 发送异步请求channel.writeAndFlush(request).addListener(f -> {if (!f.isSuccess()) {future.completeExceptionally(f.cause());}});return future.thenApply(response -> {if (response.isSuccess()) {return (User) response.getResult();} else {throw new RpcException(response.getError());}});}
}
八、RPC 的核心价值与学习要点
RPC 的本质是对分布式通信的抽象,通过 “代理 + 序列化 + 网络传输 + 服务治理” 的组合,让远程调用变得简单。要掌握 RPC,需重点理解:
- 核心流程:代理如何封装请求、网络如何传输、服务如何发现。
- 关键技术:序列化协议的选择、负载均衡策略、容错机制的设计。
- 框架实践:结合具体框架(如 Dubbo、gRPC)调试源码,理解其实现细节。