guide-rpc-framework笔记(二):核心实现

137 阅读8分钟

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

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

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

  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 负责数据传输

🎭 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() 时,实际上:

  1. 动态代理拦截 → 你的方法调用被 InvocationHandler 拦截
  2. 构建网络请求 → 封装成 RpcRequest 对象
  3. 序列化 → 将对象转换为字节流
  4. 网络传输 → 通过Socket/Netty发送到远程服务器
  5. 远程执行 → 服务端反序列化并执行真实方法
  6. 结果返回 → 通过网络将结果传回客户端
  7. 反序列化 → 将字节流转换回Java对象
  8. 返回结果 → 代理将结果返回给调用方

💡 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 = 本地调用的语法 + 远程网络请求的实现