一句话概括:一次 rpc 调用 == 通过zk获取服务节点的ip+port,然后通过请求服务端节点ip+port发起网络请求调用获取结果。
RPC 调用完整交互流程
🎯 完整交互流程图
客户端 Zookeeper 服务端(192.168.1.100:9998)
| | |
===============================启动阶段=============================
| | <-- 1.2注册zk服务地址 ---| 1.1 serviceMap.put("HelloServicetest1version1", 实例)
| | | 2. 启动 Netty 服务器
| | |
===============================运行阶段=============================
| helloService.hello() | |
| | |
| -----查询服务地址 -----> | |
| <--- 返回地址列表 ------ | |
| | |
| ----- build RpcRequest ------------------------> | 3. 解码请求
| {serviceName: "HelloServicetest1version1", | 4. serviceMap.get("HelloServicetest1version1")
| methodName: "hello", | 5. 得到 HelloServiceImpl 实例
| parameters: [Hello对象]} | 6. 反射调用 hello(Hello对象)
| | 7. 返回 "Hello description is test message"
| <---- pack RpcResponse ------------------------- |
| |
| 返回结果给业务代码 |
通信时序图
客户端 服务端
│ │
│ 1. helloService.hello() │
│ ↓ │
│ 2. RpcClientProxy.invoke() │
│ ↓ │
│ 3. NettyRpcClient.sendRpcRequest() │
│ ↓ │
│ 4. serviceDiscovery.lookupService() (查询ZK) │
│ ↓ │
│ 5. getChannel(address) (获取连接) │
│ ↓ │
│ 6. channel.writeAndFlush(rpcMessage) │
│ ────────────────────网络传输─────────────────────────→ │
│ │ 7. NettyRpcServer 接收
│ │ ↓
│ │ 8. RpcMessageDecoder 解码
│ │ ↓
│ │ 9. NettyRpcServerHandler 处理
│ │ ↓
│ │ 10. rpcRequestHandler.handle()
│ │ ↓
│ │ 11. 反射调用真实方法
│ │ ↓
│ │ 12. 构建 RpcResponse
│ ←─────────────────────网络传输───────────────────────── │ 13. ctx.writeAndFlush(response)
│ │
│ 14. NettyRpcClientHandler 接收响应 │
│ ↓ │
│ 15. 解码并返回结果 │
│ ↓ │
│ 16. 返回给客户端业务代码 │
📋 概述
RPC(Remote Procedure Call)远程过程调用的完整交互流程分为两个阶段:
- 服务启动和注册阶段(一次性)
- 运行时调用阶段(每次调用)
本文以客户端调用 HelloService.hello() 方法为例,详细描述整个交互过程。
🔄 阶段一:服务启动和注册(一次性)
1. 服务端启动
// 服务实现类
@RpcService(group = "test1", version = "version1")
public class HelloServiceImpl implements HelloService {
static {
System.out.println("HelloServiceImpl被创建");
}
@Override
public String hello(Hello hello) {
log.info("HelloServiceImpl收到: {}.", hello.getMessage());
String result = "Hello description is " + hello.getDescription();
log.info("HelloServiceImpl返回: {}.", result);
return result;
}
}
2. 服务注册 publishService
// SpringBeanPostProcessor 自动处理 @RpcService 注解
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean.getClass().isAnnotationPresent(RpcService.class)) {
// 创建服务配置
RpcServiceConfig config = RpcServiceConfig.builder()
.group("test1")
.version("version1")
.service(bean) // HelloServiceImpl 实例
.build();
// 发布服务
serviceProvider.publishService(config);
}
return bean;
}
public void publishService(RpcServiceConfig rpcServiceConfig) {
try {
String host = InetAddress.getLocalHost().getHostAddress();
// 🔥 步骤1:本地注册(存到 serviceMap)
this.addService(rpcServiceConfig);
// 🔥 步骤2:远程注册(注册到 Zookeeper)
serviceRegistry.registerService(rpcServiceConfig.getRpcServiceName(), new InetSocketAddress(host, NettyRpcServer.PORT));
} catch (UnknownHostException e) {
log.error("occur exception when getHostAddress", e);
}
}
2.1 本地注册(ServiceMap):本地服务注册表,告诉服务端"服务是什么",快速获取服务实例
作用:
- 将服务实例存储到本地的
serviceMap中 serviceMap是一个ConcurrentHashMap<String, Object>- Key:
rpcServiceName(接口名 + 版本 + 分组) - Value:具体的服务实现对象
具体实现:
// ZkServiceProviderImpl.addService()
@Override
public void addService(RpcServiceConfig rpcServiceConfig) {
String rpcServiceName = "HelloServicetest1version1"; // 接口名+组+版本
// 🔥 关键:存储到本地 ServiceMap
serviceMap.put(rpcServiceName, HelloServiceImpl实例);
log.info("Add service: {} and interfaces:{}", rpcServiceName,
HelloServiceImpl.class.getInterfaces());
}
此时 ServiceMap 内容:
serviceMap = {
"HelloServicetest1version1" -> HelloServiceImpl实例对象
}
2.2 远程注册(Zookeeper):提供服务节点 ip+port,告诉客户端"服务在哪里"(IP+端口)
作用:
- 将服务地址信息注册到 Zookeeper 注册中心
- 让其他客户端能够发现这个服务
- 创建 Zookeeper 节点:
/my-rpc/服务名/IP:端口
// 向 Zookeeper 注册服务地址
@Override
public void publishService(RpcServiceConfig rpcServiceConfig) {
String host = InetAddress.getLocalHost().getHostAddress(); // "192.168.1.100"
// 注册到 Zookeeper
serviceRegistry.registerService(
"HelloServicetest1version1",
new InetSocketAddress(host, 9998)
);
}
// ZkServiceRegistryImpl.registerService()
@Override
public void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress) {
String servicePath = "/my-rpc/HelloServicetest1version1/192.168.1.100:9998";
CuratorFramework zkClient = CuratorUtils.getZkClient();
CuratorUtils.createPersistentNode(zkClient, servicePath);
}
Zookeeper 中的数据结构:
/my-rpc
└── HelloServicetest1version1
└── 192.168.1.100:9998 -> "status:ok"
3. 启动 Netty 服务器:在 9998 端口启动Netty服务器,等待客户端连接
// NettyRpcServer 启动,监听 9998 端口
public void start() {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new RpcMessageEncoder());
ch.pipeline().addLast(new RpcMessageDecoder());
ch.pipeline().addLast(new NettyRpcServerHandler());
}
});
ChannelFuture f = b.bind("192.168.1.100", 9998).sync();
log.info("Server started on 192.168.1.100:9998");
}
🔄 阶段二:运行时调用(每次调用)
1. 客户端发起调用
// 🔄 步骤1:客户端业务代码@Component
public class UserController {
@RpcReference(group = "test1", version = "version1")
private HelloService helloService; // Spring 注入的代理对象
public String test() {
Hello hello = new Hello("world", "test message");
return helloService.hello(hello); // 🚀 RPC 调用开始!
}
}
2. 代理拦截和请求构建🌟动态代理
// 🔄 步骤2:动态代理拦截
// RpcClientProxy.invoke() 被调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
log.info("invoked method: [{}]", method.getName());
// 构建 RPC 请求
RpcRequest rpcRequest = RpcRequest.builder()
.methodName("hello")
.parameters(new Object[]{Hello对象})
.interfaceName("HelloService")
.paramTypes(new Class[]{Hello.class})
.requestId(UUID.randomUUID().toString()) // "req-12345"
.group("test1")
.version("version1")
.build();
// 发送 RPC 请求
RpcResponse<Object> rpcResponse = rpcRequestTransport.sendRpcRequest(rpcRequest);
this.check(rpcResponse, rpcRequest);
return rpcResponse.getData();
}
3. 服务发现(查询 Zookeeper)
// 🔄 步骤3:NettyRpcClient 处理网络请求
// NettyRpcClient.sendRpcRequest()
@Override
public Object sendRpcRequest(RpcRequest rpcRequest) {
CompletableFuture<RpcResponse<Object>> resultFuture = new CompletableFuture<>();
// 1. 🔥 通过 ZK 获取服务地址
InetSocketAddress inetSocketAddress = serviceDiscovery.lookupService(rpcRequest);
...接 步骤4
}
// ZkServiceDiscoveryImpl.lookupService()
@Override
public InetSocketAddress lookupService(RpcRequest rpcRequest) {
String rpcServiceName = "HelloServicetest1version1";
CuratorFramework zkClient = CuratorUtils.getZkClient();
// 从 Zookeeper 获取服务提供者列表
List<String> serviceUrlList = CuratorUtils.getChildrenNodes(zkClient, rpcServiceName);
// 返回: ["192.168.1.100:9998", "192.168.1.101:9998", "192.168.1.102:9998"]
// 负载均衡选择一个地址
String targetServiceUrl = loadBalance.selectServiceAddress(serviceUrlList, rpcRequest);
// 选择: "192.168.1.100:9998"
String[] socketAddressArray = targetServiceUrl.split(":");
return new InetSocketAddress(socketAddressArray[0],
Integer.parseInt(socketAddressArray[1]));
}
4. 建立连接和发送请求🌟BIO/NIO
// 🔄 步骤3:NettyRpcClient 处理网络请求
// NettyRpcClient.sendRpcRequest()
@Override
public Object sendRpcRequest(RpcRequest rpcRequest) {
CompletableFuture<RpcResponse<Object>> resultFuture = new CompletableFuture<>();
... 续 步骤3
// 2. 🔥 获取到服务端的 Channel
Channel channel = getChannel(inetSocketAddress);
if (channel.isActive()) {
// 3. 🔥 发送请求到服务端
unprocessedRequests.put(rpcRequest.getRequestId(), resultFuture);
RpcMessage rpcMessage = RpcMessage.builder().data(rpcRequest)
.codec(SerializationTypeEnum.HESSIAN.getCode())
.compress(CompressTypeEnum.GZIP.getCode())
.messageType(RpcConstants.REQUEST_TYPE).build();
channel.writeAndFlush(rpcMessage).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
log.info("client send message: [{}]", rpcMessage);
} else {
future.channel().close();
resultFuture.completeExceptionally(future.cause());
log.error("Send failed:", future.cause());
}
});
} else {
throw new IllegalStateException();
}
// 4. 🔥 等待服务端响应
try {
return resultFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("rpc请求失败," + e.getMessage());
}
}
5. 网络传输(编码)
// RpcMessageEncoder.encode() - 将对象编码为字节流
@Override
protected void encode(ChannelHandlerContext ctx, RpcMessage rpcMessage, ByteBuf out) {
// 写入协议头
out.writeBytes(RpcConstants.MAGIC_NUMBER); // 魔数
out.writeByte(RpcConstants.VERSION); // 版本
out.writerIndex(out.writerIndex() + 4); // 预留长度位置
out.writeByte(rpcMessage.getMessageType()); // 消息类型
out.writeByte(rpcMessage.getCodec()); // 序列化类型
out.writeByte(CompressTypeEnum.GZIP.getCode()); // 压缩类型
out.writeInt(ATOMIC_INTEGER.getAndIncrement()); // 请求ID
// 序列化请求体
Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
.getExtension("hessian");
byte[] bodyBytes = serializer.serialize(rpcMessage.getData());
// 压缩数据
Compress compress = ExtensionLoader.getExtensionLoader(Compress.class)
.getExtension("gzip");
bodyBytes = compress.compress(bodyBytes);
// 写入消息体
out.writeBytes(bodyBytes);
// 回填消息长度
int fullLength = RpcConstants.HEAD_LENGTH + bodyBytes.length;
out.setInt(5, fullLength);
}
6. 服务端接收和解码
// RpcMessageDecoder.decode() - 将字节流解码为对象
private Object decodeFrame(ByteBuf in) {
// 读取协议头
checkMagicNumber(in);
checkVersion(in);
int fullLength = in.readInt();
byte messageType = in.readByte();
byte codecType = in.readByte();
byte compressType = in.readByte();
int requestId = in.readInt();
// 读取消息体
int bodyLength = fullLength - RpcConstants.HEAD_LENGTH;
byte[] bs = new byte[bodyLength];
in.readBytes(bs);
// 解压数据
Compress compress = ExtensionLoader.getExtensionLoader(Compress.class)
.getExtension("gzip");
bs = compress.decompress(bs);
// 反序列化
Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
.getExtension("hessian");
RpcRequest rpcRequest = serializer.deserialize(bs, RpcRequest.class);
// 构建 RPC 消息
RpcMessage rpcMessage = RpcMessage.builder()
.codec(codecType)
.requestId(requestId)
.messageType(messageType)
.data(rpcRequest)
.build();
return rpcMessage;
}
7. 服务端处理请求
// NettyRpcServerHandler.channelRead()
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof RpcMessage) {
RpcMessage rpcMessage = (RpcMessage) msg;
RpcRequest rpcRequest = (RpcRequest) rpcMessage.getData();
// 🔥 关键:处理 RPC 请求
Object result = rpcRequestHandler.handle(rpcRequest);
// 构建响应
RpcResponse<Object> rpcResponse = RpcResponse.success(result,
rpcRequest.getRequestId());
RpcMessage responseMessage = new RpcMessage();
responseMessage.setData(rpcResponse);
responseMessage.setMessageType(RpcConstants.RESPONSE_TYPE);
// 发送响应
ctx.writeAndFlush(responseMessage);
}
}
8. 服务查找和反射调用
// 1. 服务端接收到RPC请求
// 2. RpcRequestHandler处理请求
public Object handle(RpcRequest rpcRequest) {
// 从serviceMap中获取本地服务实例
Object service = serviceProvider.getService(rpcRequest.getRpcServiceName());
return invokeTargetMethod(rpcRequest, service);
}
// 3. ZkServiceProviderImpl.getService()
public Object getService(String rpcServiceName) {
// 从serviceMap中查找服务实例
Object service = serviceMap.get(rpcServiceName);
if (null == service) {
throw new RpcException(RpcErrorMessageEnum.SERVICE_CAN_NOT_BE_FOUND);
}
return service;
}
// 4. 通过反射调用具体方法
private Object invokeTargetMethod(RpcRequest rpcRequest, Object service) {
Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
result = method.invoke(service, rpcRequest.getParameters());
return result;
}
9. 响应返回和异步完成
// 客户端接收响应
// NettyRpcClientHandler.channelRead()
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof RpcMessage) {
RpcMessage rpcMessage = (RpcMessage) msg;
if (rpcMessage.getMessageType() == RpcConstants.RESPONSE_TYPE) {
RpcResponse<Object> rpcResponse = (RpcResponse<Object>) rpcMessage.getData();
// 🎯 完成对应的 CompletableFuture
CompletableFuture<RpcResponse<Object>> future =
unprocessedRequests.remove(rpcResponse.getRequestId());
if (future != null) {
future.complete(rpcResponse);
}
}
}
}
// 客户端获取结果
try {
RpcResponse<Object> response = resultFuture.get(); // 阻塞等待响应
return response; // 返回给代理对象
} catch (Exception e) {
throw new RuntimeException("rpc请求失败," + e.getMessage());
}
// 最终返回给业务代码
String result = helloService.hello(hello); // "Hello description is test message"
💡 关键理解点
核心组件作用
| 组件 | 作用 | 存储内容 |
|---|---|---|
| ServiceMap | 本地快速查找服务实例,执行业务逻辑 | Map<服务名, Java对象实例> |
| Zookeeper | 全局服务发现,告诉客户端去哪台机器调用 | 服务名 -> 地址列表 |
🎯 总结
说白了整个 rpc 的过程就是:
- 通过 zk 获取服务节点地址;
- 通过节点的本地 servicemap 获取服务实例;
- build RpcRequest;
- 通过反射调用服务实例方法;
- pack RpcResponse。
1️⃣ 动态代理拦截
helloService.hello()
↓
RpcClientProxy.invoke() 被调用
2️⃣ 构建 RPC 请求
RpcRequest {
interfaceName: "HelloService"
methodName: "hello"
parameters: [Hello对象]
group: "test1"
version: "version1"
}
3️⃣ 🔍 通过 ZK 获取服务地址
rpcServiceName = "HelloServicetest1version1"
↓
ZK 查询: /my-rpc/HelloServicetest1version1/
↓
返回: ["192.168.1.100:9998", "192.168.1.101:9998"]
↓
负载均衡选择: "192.168.1.100:9998"
4️⃣ 🚀 序列化 + 网络传输
RpcRequest 对象
↓ Hessian序列化
byte[] 字节数组
↓ Netty传输
发送到 192.168.1.100:9998
5️⃣ 🎯 服务端接收处理
Netty接收字节数组
↓ Hessian反序列化
RpcRequest 对象
↓
RpcRequestHandler.handle()
6️⃣ 📋 通过本地 ServiceMap 获取服务实例
rpcServiceName = "HelloServicetest1version1"
↓
serviceMap.get("HelloServicetest1version1")
↓
返回: HelloServiceImpl 实例对象
7️⃣ ⚡ 反射调用真实方法
Method method = HelloServiceImpl.class.getMethod("hello", Hello.class);
Object result = method.invoke(HelloServiceImpl实例, Hello对象);
↓
返回: "Hello description is 描述"
8️⃣ 🔄 结果返回
result
↓ Hessian序列化
byte[] 字节数组
↓ Netty传输
返回给客户端
↓ Hessian反序列化
客户端收到: "Hello description is 描述"
RPC 的本质就是:
- 服务发现:ZK 告诉我服务在哪里
- 本地查找:ServiceMap 告诉我具体的服务实例是什么
- 反射调用:Java 反射机制调用真实方法
- 网络传输:序列化 + Netty 负责数据传输
🎭 RPC的"魔法"本质
表面现象 vs 实际执行
表面上看起来:
// 看起来像本地方法调用
String result = helloService.hello(new Hello("111", "222"));
实际上发生的:
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 1. 创建一个PRC请求
log.info("invoked method: [{}]", method.getName());
RpcRequest rpcRequest = RpcRequest.builder().methodName(method.getName())
.parameters(args)
.interfaceName(method.getDeclaringClass().getName())
.paramTypes(method.getParameterTypes())
.requestId(UUID.randomUUID().toString())
.group(rpcServiceConfig.getGroup())
.version(rpcServiceConfig.getVersion())
.build();
// 2. 发送RPC请求 - 这里就是网络请求!
RpcResponse<Object> rpcResponse = (RpcResponse<Object>) rpcRequestTransport.sendRpcRequest(rpcRequest);
this.check(rpcResponse, rpcRequest);
return rpcResponse.getData();
}
🌐 网络请求的真相
每次你调用 helloService.hello() 时,实际上:
- 动态代理拦截 → 你的方法调用被 InvocationHandler 拦截
- 构建网络请求 → 封装成 RpcRequest 对象
- 序列化 → 将对象转换为字节流
- 网络传输 → 通过Socket/Netty发送到远程服务器
- 远程执行 → 服务端反序列化并执行真实方法
- 结果返回 → 通过网络将结果传回客户端
- 反序列化 → 将字节流转换回Java对象
- 返回结果 → 代理将结果返回给调用方
💡 RPC的价值在于"透明化"
RPC框架的核心价值不是消除网络请求,而是:
✅ 简化开发体验
// 不用RPC,你需要这样写:
HttpClient client = new HttpClient();
String json = objectMapper.writeValueAsString(hello);
HttpResponse response = client.post("http://server:8080/hello", json);
String result = objectMapper.readValue(response.getBody(), String.class);
// 有了RPC,只需要:
String result = helloService.hello(hello);
✅ 屏蔽技术细节
- 🌐 网络通信:不用关心Socket、HTTP、协议等
- 📝 序列化:不用手动处理对象与字节流转换
- 🔍 服务发现:不用关心服务在哪台机器上
- ⚖️ 负载均衡:不用手动选择服务实例
- ⚠️ 异常处理:统一的异常处理机制
✅ 提供分布式能力
- 🔄 重试机制:网络失败自动重试
- 💓 健康检查:自动检测服务可用性
- 📊 监控统计:调用次数、耗时等指标
- 🛡️ 容错降级:服务不可用时的降级策略
RPC = 本地调用的语法 + 远程网络请求的实现