guide-rpc-framework笔记

链接:github.com/Snailclimb/...


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)远程过程调用的完整交互流程分为两个阶段:

  1. 服务启动和注册阶段(一次性)
  2. 运行时调用阶段(每次调用)

本文以客户端调用 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 的过程就是:

  1. 通过 zk 获取服务节点地址;
  2. 通过节点的本地 servicemap 获取服务实例;
  3. build RpcRequest;
  4. 通过反射调用服务实例方法;
  5. 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 的本质就是:

  1. 服务发现:ZK 告诉我服务在哪里
  2. 本地查找:ServiceMap 告诉我具体的服务实例是什么
  3. 反射调用:Java 反射机制调用真实方法
  4. 网络传输:序列化 + Netty 负责数据传输
相关推荐
37手游后端团队3 小时前
Claude Code Review:让AI审核更懂你的代码
人工智能·后端·ai编程
长安不见3 小时前
解锁网络性能优化利器HTTP/2C
后端
LSTM974 小时前
使用Python对PDF进行拆分与合并
后端
用户298698530144 小时前
C#:将 HTML 转换为图像(Spire.Doc for .NET 为例)
后端·.net
程序员小假5 小时前
为什么这些 SQL 语句逻辑相同,性能却差异巨大?
java·后端
泉城老铁5 小时前
导出大量数据时如何优化内存使用?SXSSFWorkbook的具体实现方法是什么?
spring boot·后端·excel
渣哥5 小时前
从配置文件到 SpEL 表达式:@Value 在 Spring 中到底能做什么?
javascript·后端·面试
文心快码BaiduComate5 小时前
开工不累,双强护航:文心快码接入 DeepSeek-V3.2-Exp和 GLM-4.6,助你节后高效Coding
前端·人工智能·后端