2026/5/21 17:20:58
网站建设
项目流程
企业的网站做一个要多少,微信第三方做网站需要费用吗,wordpress文章添加seo标题,空间设计和室内设计的区别这是一个非常硬核且经典的问题。要真正理解 RPC#xff08;Remote Procedure Call#xff0c;远程过程调用#xff09;和 HTTP 的区别#xff0c;以及如何手写一个 RPC 框架#xff0c;我们需要深入操作系统的网络层、IO 模型以及序列化协议。第一部分#xff1a;RPC 与 …这是一个非常硬核且经典的问题。要真正理解 RPCRemote Procedure Call远程过程调用和 HTTP 的区别以及如何手写一个 RPC 框架我们需要深入操作系统的网络层、IO 模型以及序列化协议。第一部分RPC 与 传统 HTTP 的区别1. 概念上的误区首先要纠正一个常见的误区RPC 和 HTTP 不是对立的。RPC是一种设计思想我要像调用本地函数一样调用远程函数。HTTP是一个传输协议。事实上很多 RPC 框架如 gRPC底层用的就是 HTTP/2。我们通常所说的 RPC vs HTTP实际上是在比较RPC 风格如 gRPC, Dubbo与RESTful 风格基于 HTTP/1.1 JSON。2. 核心区别对比特性RPC (以 gRPC/Dubbo 为例)RESTful HTTP (传统 Web API)设计初衷动作导向。隐藏网络细节让远程调用像本地调用一样service.getUser(id)。资源导向。操作资源的状态GET /users/1。传输协议通常基于TCP或HTTP/2。通常基于HTTP/1.1。报文体积极小。通常使用二进制序列化Protobuf, Thrift, Hessian没有冗余 Header。较大。使用文本JSON/XML且 HTTP 1.1 Header 通常包含大量元数据。性能高。基于二进制、TCP 长连接、多路复用。中等。JSON 解析慢HTTP/1.1 存在队头阻塞问题。开发体验强类型。通常需要 IDL 文件如.proto生成代码客户端服务端强契约。弱类型。接口灵活通常看文档对接容易出现字段拼写错误。适用场景微服务内部通信追求低延迟、高吞吐。对外接口如移动端、Web 前端因为 HTTP 通用性最好。总结HTTP (REST)像寄信格式通用谁都能读但信封Header很厚且一来一回慢。RPC (TCP/Binary)像专线电话语言精简二进制双方约定暗号IDL连接建立后说话极快。第二部分涉及的计算机网络硬核知识要手搓 RPC你必须解决网络通信中的三大核心问题。这也是计算机网络的精髓。1. 寻址与传输Layer 4 - Transport LayerSocket 编程RPC 的本质是网络通信。你需要使用 Socket API在 Java 中是Socket/ServerSocket在 Go 中是net.Dial/net.Listen。TCP 连接复用建立 TCP 连接三次握手很慢。成熟的 RPC 框架都会使用连接池或长连接避免每次调用都握手。2. 序列化与反序列化Layer 6 - Presentation Layer网络只能传输 0 和 1字节流不能传输内存中的对象如 Java 的 Object 或 Go 的 Struct。序列化Marshaling把内存对象变成二进制串。反序列化Unmarshaling把二进制串变回内存对象。手搓选择为了简单我们可以用 JSON。为了性能通常用 Protobuf 或 Hessian。3. 拆包与粘包TCP 字节流特性这是很多初学者最容易忽略的点。TCP 是面向字节流的协议它没有消息的概念。粘包你发送了两个请求 ABC 和 DEFTCP 为了效率可能会把它们合并成 ABCDEF 发送过去。拆包你发送了一个很大的包TCP 可能会把它拆成两段发送。解决方案自定义应用层协议我们需要定义一个协议格式。最常用的方式是Length-Prefix长度前缀法。格式[消息长度 (4字节)] [消息体 (N字节)]读取时先读4个字节拿到长度 N再读取 N 个字节这样才能保证读到的是一个完整的 RPC 请求。第三部分如何手搓一个简单的 RPC我们以Java为例因为 Java 的动态代理最适合演示 RPC 原理逻辑通用于所有语言。架构图[Client App] --调用-- [Client Stub (Proxy)] --序列化-- [Network] | [Server Impl] --反射调用-- [Server Skeleton] --反序列化--|步骤 1: 定义公共接口客户端和服务端都需要这个接口。public interface UserService { User getUser(Integer id); }步骤 2: 客户端代理 (The Magic)客户端并没有UserService的实现类怎么调用用动态代理。代理的作用是拦截方法调用把方法名、参数打包发给服务端。// 这是一个极简的动态代理逻辑 public class RpcClientProxy implements InvocationHandler { private String host; private int port; public RpcClientProxy(String host, int port) { this.host host; this.port port; } // 当你调用 userService.getUser(1) 时会被这个 invoke 方法拦截 Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 封装请求对象 (包含接口名、方法名、参数类型、参数值) RpcRequest request new RpcRequest(); request.setClassName(method.getDeclaringClass().getName()); request.setMethodName(method.getName()); request.setParamTypes(method.getParameterTypes()); request.setArgs(args); // 2. 建立网络连接 (Socket) Socket socket new Socket(host, port); // 3. 发送请求 (序列化) - 这里偷懒用了 Java 自带的序列化实际应用应换成 JSON/Protobuf ObjectOutputStream output new ObjectOutputStream(socket.getOutputStream()); output.writeObject(request); // 发送数据 // 4. 接收响应 (阻塞等待) ObjectInputStream input new ObjectInputStream(socket.getInputStream()); Object result input.readObject(); // 获取结果 // 5. 清理资源 socket.close(); // 6. 返回结果给调用者 return result; } // 获取代理对象的工具方法 public T T getProxy(ClassT interfaceClass) { return (T) Proxy.newProxyInstance( interfaceClass.getClassLoader(), new Class?[]{interfaceClass}, this ); } }步骤 3: 服务端监听 (The Server)服务端需要在一个端口一直监听接收请求找到对应的实现类执行代码。public class RpcServer { // 存储接口和服务实现类的映射关系 private MapString, Object serviceRegistry new HashMap(); public void register(Class interfaceClass, Object impl) { serviceRegistry.put(interfaceClass.getName(), impl); } public void start(int port) throws Exception { ServerSocket serverSocket new ServerSocket(port); System.out.println(RPC Server started on port port); while (true) { // 1. 监听连接 (BIO 模型阻塞) Socket socket serverSocket.accept(); // 2. 开启线程处理 (为了支持并发实际应该用线程池) new Thread(() - { try { ObjectInputStream input new ObjectInputStream(socket.getInputStream()); // 3. 读取请求 (反序列化) RpcRequest request (RpcRequest) input.readObject(); // 4. 从注册表中找到具体的实现类 Object serviceImpl serviceRegistry.get(request.getClassName()); // 5. 通过反射调用真实方法 Method method serviceImpl.getClass().getMethod( request.getMethodName(), request.getParamTypes() ); Object result method.invoke(serviceImpl, request.getArgs()); // 6. 写回结果 ObjectOutputStream output new ObjectOutputStream(socket.getOutputStream()); output.writeObject(result); } catch (Exception e) { e.printStackTrace(); } }).start(); } } }步骤 4: 跑起来服务端代码UserService service new UserServiceImpl(); // 真实的业务逻辑 RpcServer server new RpcServer(); server.register(UserService.class, service); server.start(8080);客户端代码RpcClientProxy proxy new RpcClientProxy(localhost, 8080); UserService userService proxy.getProxy(UserService.class); // 这一步看起来像本地调用实际上走了网络 User user userService.getUser(1001); System.out.println(user.getName());第四部分进阶思考从玩具到生产级框架上面的代码只是一个玩具Toy RPC要变成一个生产级框架如 Dubbo/gRPC还需要解决以下问题这也体现了计算机网络的高级应用IO 模型BIO vs NIO上面的代码使用了ServerSocket.accept()和Stream.read()这是同步阻塞 IO (BIO)。如果不开启多线程一个客户端卡住整个服务端就挂了。优化使用Java NIO (Netty)或 Go 的 Goroutine。使用IO 多路复用 (epoll)技术让一个线程管理成千上万个连接。序列化性能Java 原生序列化ObjectOutputStream非常慢且生成的数据包很大。优化使用Protobuf, Kryo, Hessian。它们基于二进制位操作体积可能只有 Java 原生的 1/10。服务发现Service Discovery上面代码写死了localhost:8080。如果是集群呢优化引入Zookeeper/Nacos/Etcd。服务端启动时把 IP 注册上去客户端调用时先去注册中心拉取 IP 列表然后进行负载均衡。自定义协议解决粘包上面的代码依赖 Java 对象的边界。如果跨语言Java 调 Go必须定义字节级协议。优化定义Header(包含 Magic Number, Version, Length) Body。总结RPC 的本质就是透明化网络通信。网络层利用 TCP/Socket 传输数据。表示层利用序列化解决对象传输问题。应用层利用动态代理Stub让调用者无感知。