RPC 调用完整交互流程
🎯 完整交互流程图
css
客户端 Zookeeper 服务端(192.168.1.100:9998)
| | |
===============================启动阶段=============================
| | <-- 1.2注册zk服务地址 ---| 1.1 serviceMap.put("HelloServicetest1version1", 实例)
| | | 2. 启动 Netty 服务器
| | |
===============================运行阶段=============================
| helloService.hello() | |
| | |
| 查询服务地址 ----------> | |
| <-- 返回地址列表 ------ | |
| | |
| 发送 RpcRequest --------------------------------> | 3. 解码请求
| {serviceName: "HelloServicetest1version1", | 4. serviceMap.get("HelloServicetest1version1")
| methodName: "hello", | 5. 找到 HelloServiceImpl 实例
| parameters: [Hello对象]} | 6. 反射调用 hello(Hello对象)
| | 7. 返回 "Hello description is test message"
| <-- 返回 RpcResponse --------------------------- |
| |
| 返回结果给业务代码 |
📋 概述
RPC(Remote Procedure Call)远程过程调用的完整交互流程分为两个阶段:
- 服务启动和注册阶段(一次性)
- 运行时调用阶段(每次调用)
本文以客户端调用 HelloService.hello()
方法为例,详细描述整个交互过程。
🔄 阶段一:服务启动和注册(一次性)
1. 服务端启动
typescript
// 服务实现类
@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
// 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();
this.addService(rpcServiceConfig);
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
// 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 内容:
ini
serviceMap = {
"HelloServicetest1version1" -> HelloServiceImpl实例对象
}
2.2 远程注册(Zookeeper)| 提供服务节点 ip+port
作用:
- 将服务地址信息注册到 Zookeeper 注册中心
- 让其他客户端能够发现这个服务
- 创建 Zookeeper 节点:
/my-rpc/服务名/IP:端口
typescript
// 向 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 中的数据结构:
bash
/my-rpc
└── HelloServicetest1version1
└── 192.168.1.100:9998 -> "status:ok"
3. 启动 Netty 服务器 | 在 9998 端口启动 Netty 服务器,等待客户端连
scss
// 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. 客户端发起调用
typescript
// 业务代码调用
@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. 代理拦截和请求构建
typescript
// 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)
ini
// NettyRpcClient.sendRpcRequest()
@Override
public Object sendRpcRequest(RpcRequest rpcRequest) {
CompletableFuture<RpcResponse<Object>> resultFuture = new CompletableFuture<>();
// 🔍 服务发现:从 Zookeeper 获取服务地址
InetSocketAddress inetSocketAddress = serviceDiscovery.lookupService(rpcRequest);
// 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. 建立连接和发送请求
scss
// 获取或创建 Netty Channel
Channel channel = getChannel(inetSocketAddress); // 连接到 192.168.1.100:9998
if (channel.isActive()) {
// 存储请求 Future,用于异步响应
unprocessedRequests.put(rpcRequest.getRequestId(), resultFuture);
// 构建 RPC 消息
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());
}
});
}
5. 网络传输(编码)
scss
// 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. 服务端接收和解码
ini
// 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. 服务端处理请求
ini
// 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. 服务查找和反射调用
typescript
// RpcRequestHandler.handle()
public Object handle(RpcRequest rpcRequest) {
// 🔍 从本地 ServiceMap 查找服务实例
String serviceName = "HelloServicetest1version1";
Object service = serviceProvider.getService(serviceName);
// 返回: HelloServiceImpl 实例对象
return invokeTargetMethod(rpcRequest, service);
}
// 反射调用真实方法
private Object invokeTargetMethod(RpcRequest rpcRequest, Object service) {
try {
// 获取方法
Method method = service.getClass().getMethod("hello", Hello.class);
// 🚀 反射调用
Object result = method.invoke(service, Hello对象);
// 实际执行: HelloServiceImpl.hello(Hello对象)
// 返回: "Hello description is test message"
log.info("service:[{}] successful invoke method:[{}]",
rpcRequest.getInterfaceName(), rpcRequest.getMethodName());
return result;
} catch (Exception e) {
throw new RpcException(e.getMessage(), e);
}
}
9. 响应返回和异步完成
typescript
// 客户端接收响应
// 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 负责数据传输