简易版RPC框架实现

RPC框架:核心概念与使用价值(新手易懂版)

RPC框架是分布式系统中的核心概念------简单来说,RPC(远程过程调用)框架是让你像调用本地函数一样调用「另一台服务器上的函数」的工具,无需关心网络通信、数据序列化等底层细节。

一、先搞懂:什么是RPC框架?

1. 核心定义(人话版)

  • 本地调用 :你在Java代码里调用 Math.max(1,2),或在Python里调用 list.append(3),都是「进程内」的函数调用,直接在内存中完成;
  • 远程调用:你的代码部署在服务器A,需要调用服务器B上的「用户余额查询函数」,这就是「跨机器」的远程调用;
  • RPC框架 :封装了远程调用的所有底层细节(网络传输、数据格式转换、异常处理等),让你写 userService.getBalance(1001) 时,感觉就像调用本地函数一样,完全感知不到「跨机器」的存在。

2. RPC的核心特征

特征 解释
透明性 调用远程函数 ≈ 调用本地函数,开发者无需关注网络、序列化等底层逻辑
跨语言/跨平台 比如Java服务可以调用Python服务,Windows服务器可以调用Linux服务器
高性能 相比HTTP接口(如RESTful),RPC通常采用更高效的序列化协议和通信模型
可扩展性 内置负载均衡、服务发现、熔断降级等分布式必备能力

3. RPC框架的工作流程(简化版)

用「客户端调用服务器B的余额查询函数」举例,RPC框架会自动完成以下步骤:

yaml 复制代码
graph TD
    A[客户端代码调用 getBalance(1001)] --> B[RPC客户端SDK]
    B --> C[序列化参数:把1001转成二进制字节流]
    C --> D[网络通信:通过TCP/UDP把字节流发给服务器B]
    D --> E[RPC服务器端SDK]
    E --> F[反序列化:把字节流转回1001]
    F --> G[调用服务器B的本地getBalance函数]
    G --> H[得到结果:比如99元]
    H --> I[序列化结果:99转字节流]
    I --> J[网络回传:把结果发给客户端]
    J --> K[反序列化结果:字节流转99]
    K --> L[客户端代码拿到99元]

整个过程中,开发者只需要写 getBalance(1001) 和处理返回值,其余步骤全由RPC框架自动完成。

二、什么时候不用RPC框架?

RPC不是万能的,以下场景优先用HTTP接口(如RESTful):

  • 跨平台开放接口:比如给第三方开发者提供的接口(如微信支付API),HTTP+JSON更通用,无需对方接入你的RPC SDK;
  • 简单的小项目:单机/少量服务的项目,远程调用少,没必要引入复杂的RPC框架;
  • 需要浏览器访问的接口:浏览器只能发起HTTP请求,无法直接调用RPC接口。

三、主流RPC框架举例

不同语言/场景有不同的主流框架,核心功能类似:

框架 语言/特点
Dubbo 阿里开源,Java为主,国内微服务主流框架,适配Spring生态
gRPC Google开源,跨语言(C++/Java/Python/Go等),基于HTTP/2+Protobuf
Thrift Facebook开源,跨语言,支持多种传输协议和序列化协议
Spring Cloud OpenFeign Java,基于HTTP的伪RPC(本质是HTTP封装),适配Spring Cloud生态
Motan 微博开源,Java/Go,支持多注册中心、跨语言

总结(核心要点)

  1. RPC框架的本质:封装远程调用的底层细节,让「跨机器调用函数」像「本地调用」一样简单;
  2. 使用RPC的核心原因 :
    • 提效:避免手写网络、序列化等重复代码;
    • 稳性:内置服务发现、熔断、负载均衡等分布式能力;
    • 高性能:更优的通信/序列化协议,适配高并发;
    • 适配微服务:支撑大规模服务间的远程调用;
  3. RPC vs HTTP:RPC适合内部微服务调用,HTTP适合开放接口/浏览器访问;
  4. 核心价值:让开发者聚焦业务逻辑,而非分布式底层细节。

简单记:RPC框架是分布式系统的「远程调用管家」,帮你搞定跨机器调用的所有脏活累活。

下面用一张图来说下其执行流程

  1. 消费者(客户端)发起调用

    • 消费者 :业务代码所在的客户端,它只需要调用 orderService.order(...) 方法,完全感知不到这是一个远程调用。
    • 代理对象 orderService:这是RPC框架生成的动态代理,它会拦截这个调用请求,把本地调用转换成RPC框架能处理的远程请求。
  2. RPC框架客户端处理

    • 请求客户端(HTTP/其他):代理对象将调用信息(接口名、方法名、参数)交给请求客户端,由它负责网络通信。
    • 序列化/反序列化:在发送请求前,请求客户端会把调用信息序列化为二进制字节流(比如用JSON、Protobuf),以便在网络中高效传输。
    • 网络发送:请求客户端通过HTTP或其他协议(如TCP),将序列化后的请求发送给服务端的Web服务器。
  3. RPC框架服务端处理

    • Web服务器:监听并接收客户端的请求,然后将请求转发给「请求处理器」。
    • 请求处理器:接收请求后,先对字节流进行反序列化,还原出原始的调用信息(接口名、方法名、参数)。
    • 本地服务注册器 :根据接口名 orderService,在注册器中找到对应的本地业务实现类(服务提供者的真实代码)。
  4. 服务提供者执行方法

    • 提供者(orderService接口/order方法) :本地服务注册器定位到实现类后,调用其 order 方法执行真实的业务逻辑(比如创建订单、查询数据)。

代码后续会上传到github,这里先记录下项目结构与模块划分,方便大家理解。

项目结构与模块划分

这个简易RPC框架项目分为四个主要模块,各模块职责明确,分工协作:

1. 核心框架模块(feng-rpc-easy)

这是RPC框架的核心实现,包含以下子模块:

1.1 数据模型层(model)
  • RpcRequest.java:RPC请求数据结构,包含服务名、方法名、参数类型和参数值
  • RpcResponse.java:RPC响应数据结构,包含响应数据、响应消息、异常信息和数据类型
1.2 序列化层(serializer)
  • Serializer.java:序列化接口,定义了序列化和反序列化方法
  • JDKSerializer.java:JDK序列化实现,使用ObjectOutputStream和ObjectInputStream进行序列化和反序列化
1.3 服务注册层(registry)
  • LocalRegistry.java:本地服务注册表,使用ConcurrentHashMap存储服务名和对应的实现类
1.4 代理层(proxy)
  • ServiceProxy.java:动态代理实现,实现InvocationHandler接口,负责将方法调用转换为RPC请求
  • ServiceProxyFactory.java:代理工厂,用于创建服务代理实例
1.5 服务器层(server)
  • HttpServer.java:HTTP服务器接口,定义了启动服务器的方法
  • VertxHttpServer.java:使用Vert.x实现的HTTP服务器
  • HttpServerHandler.java:HTTP请求处理器,负责处理RPC请求的核心逻辑

2. 公共模块(example-common)

定义了客户端和服务端共享的数据模型和服务接口:

  • User.java:用户数据模型
  • UserService.java:用户服务接口

3. 服务提供者模块(example-provider)

实现了服务接口并提供服务:

  • UserServiceImpl.java:UserService接口的实现类
  • EasyProviderExample.java:服务提供者启动类,负责注册服务和启动服务器

4. 服务消费者模块(example-consumer)

调用远程服务:

  • UserServiceProxy.java:静态代理实现(已被动态代理替代)
  • EasyConsumerExample.java:服务消费者启动类,负责获取服务代理并调用服务

核心实现原理

1. 服务注册与发现

服务提供者在启动时,通过LocalRegistry.register()方法将服务接口名和实现类注册到本地注册表中。服务消费者通过服务接口名从注册表中获取服务实现类。

2. 序列化与反序列化

使用JDK自带的序列化机制,将Java对象转换为字节数组进行网络传输,接收方再将字节数组转换回Java对象。

3. 动态代理

客户端使用JDK动态代理生成服务代理实例,当调用代理方法时,代理会将方法调用转换为RpcRequest对象,然后通过HTTP发送到服务端。

4. HTTP通信

使用Vert.x实现的HTTP服务器处理客户端请求,服务端接收到请求后,反序列化得到RpcRequest对象,然后通过反射调用对应的服务方法,最后将结果序列化为RpcResponse对象返回给客户端。

工作流程

服务提供者

  1. 实现公共接口(如UserServiceImpl实现UserService接口)
  2. 将服务注册到本地注册表:LocalRegistry.register(UserService.class.getName(), UserServiceImpl.class)
  3. 启动HTTP服务器:vertxHttpServer.doStart(8080)

服务消费者

  1. 获取服务代理:UserService userService = ServiceProxyFactory.getProxy(UserService.class)
  2. 调用代理方法:User newUser = userService.getUser(user)
  3. 代理将方法调用转换为RpcRequest对象
  4. 序列化RpcRequest并发送HTTP请求:HttpRequest.post("http://localhost:8080").body(bodyBytes).execute()
  5. 接收响应并反序列化为RpcResponse对象
  6. 返回结果给调用者:return (User) rpcResponse.getData()

服务端处理

  1. 接收HTTP请求:HttpServerHandler.handle()方法
  2. 反序列化请求体得到RpcRequest对象
  3. 从本地注册表获取服务实现类:LocalRegistry.get(serviceName)
  4. 通过反射调用服务方法:method.invoke(clazz.newInstance(), rpcRequest.getArgs())
  5. 创建RpcResponse对象并设置结果
  6. 序列化RpcResponse并返回响应:doResponse(request, rpcResponse, serializer)

实现注意事项

1. 服务名一致性

服务注册和调用必须使用相同的服务名,否则会导致服务查找失败。在这个项目中,服务名使用接口的全限定名(如UserService.class.getName())。

2. 序列化方式统一

客户端和服务端必须使用相同的序列化方式,否则会导致反序列化失败。这个项目中使用JDK序列化,要求所有传输的对象都实现Serializable接口。

3. 异常处理

目前的异常处理比较简单,只是打印异常信息,没有完善的异常传递机制。在实际应用中,需要将服务端的异常正确传递给客户端。

4. 服务发现机制

当前使用的是本地注册表,只适用于单机环境。在分布式环境中,需要使用远程注册中心(如ZooKeeper、Nacos等)。

5. 负载均衡

当前没有实现负载均衡机制,客户端只能调用固定地址的服务。在实际应用中,需要实现负载均衡来分发请求。

6. 配置化

端口、地址等信息目前是硬编码的,在实际应用中,应该通过配置文件或配置中心进行配置。

7. 线程安全

本地注册表使用ConcurrentHashMap保证线程安全,适合多线程环境下使用。

8. 资源释放

在序列化和反序列化过程中,需要注意关闭流资源,避免资源泄漏。

相关推荐
贞明电子8 小时前
工业三色报警灯怎么接入项目系统?开关量与RS485控制应用指南
运维·网络·微信公众平台·贞明电子
明天也要努力学习8 小时前
HTTP 协议的基本格式和 fiddler 的用法
网络协议·http·fiddler
HideF8 小时前
Linux
linux·服务器·网络
万联WANFLOW8 小时前
海外网络故障怎么排查?Cloudflare 三季度断网报告解读与 mtr 实战
运维·网络·故障分析
sbjdhjd8 小时前
网工进阶笔记:H3CSE 园区网全栈技术与协议汇总
网络·笔记·云计算·ssh·网络工程·vlan·aaa
taktok_ai11 小时前
用 cc-switch 连接 Taktok
网络
小嘉丶学长13 小时前
Day14 · 字典与集合
java·开发语言·网络·python·学习·1024程序员节
pt104314 小时前
Cisco设备网络安全系列-18:SIEM与SOAR安全解决方案
网络·安全
Tel1992530800415 小时前
温度湿度信号转WiFi/MQTT/Modbus TCP采集模块 无线传输
网络·网络协议·tcp/ip·工业自动化·工控设备
yyuuuzz15 小时前
云服务器安全组规则优先级的机制记录
运维·服务器·网络·安全·阿里云·aws