前言
距离上一次写正儿八经的技术文有个半年多了,抱歉抱歉ಥ_ಥ,确实有点怠惰了。工作强度吧,真的有点大,心力耗费有点严重,同时博主的相亲之路也不是很顺畅,北京的相亲市场简直就是神仙赶集(┬┬﹏┬┬),再加上工作和生活上事儿赶事儿,就慢慢躺了。不过心里确实还记着要写技术博客,也没耽搁,目前存稿和思路后面会出两篇,一篇RPC,一篇缓存相关。
本文是RPC的经典一本通文章,因为RPC是一个偏概念的东西,所以会搭配一个DEMO来讲解,同时相关的知识会尽可能的丰富一下,保持我一贯的文章水准。文章从RPC的定义和特征讲起,介绍核心流程和关键组件,接着会对一些名词做一下解释,同时回答一些问题。最后会用一个DEMO来介绍如何写一个RPC框架,当然也会拓展序列化、压缩、负载均衡和动态代理的知识,汇总网上的资料横向对比各种框架的性能和选型思路。本文的目的就是去帮助大家学习和认识平时最熟悉的基础框架-RPC组件的原理,同时拓展的知识也能帮助我们加深对其他中间件的理解。
定义和原理
什么是RPC?
RPC(Remote Procedure Call,远程过程调用 )是远程过程调用的缩写,是一种计算机通信协议 。它使得程序可以像调用本地函数一样,请求另一个进程或者计算机上的服务,而不需要了解底层网络技术。RPC可以简化分布式系统的开发,提高系统的可维护性和可扩展性。RPC协议的实现包括一个客户端和一个服务端,它们可以运行在不同的机器上。
核心目标: 简化分布式系统中跨进程/跨机器的交互,让你调用远程方法像调用本地方法一样简单。
核心特征:
- 透明性:调用远程方法像本地方法
- 通信封装:隐藏网络细节(序列化、传输、路由)
- 协议自由:支持多种底层协议(TCP/HTTP/triple/dubbo/自定义)
- 高效性:低延迟、高吞吐(二进制协议+长连接)
现代的RPC框架,部分还会提供服务治理能力 (服务注册与发现、负载均衡、容错机制等功能)、跨语言支持 (通过接口定义语言(IDL)生成多语言客户端代码,实现跨平台调用)、多样化通信模式(支持同步、异步调用,以及流式通信。例如,gRPC支持双向流式数据传输,适用于实时交互场景)
举个例子: 用交流来说,远程调用就相当于打电话沟通或者微信聊天,本地调用就是面对面说话。
核心原理
关键组件
- 客户端(服务消费端):调用远程方法的一端。
- 客户端Stub(存根) : 客户端Stub是远程服务在客户端的本地代理,负责将调用请求序列化为网络传输格式(如二进制流或JSON),并通过网络发送至服务端
- 网络传输 :将客户端的调用方法的信息传到服务端,等服务端执行完毕后把返回结果再传输回客户端。实现方式有最基础的Socket,基于Socket封装的Netty,还有Http以及各种自定义方式。
- 服务端Skeleton(骨架): 服务端Skeleton是服务端接收请求的代理,负责反序列化客户端请求,调用本地实际服务方法,并将结果序列化后返回给客户端
- 服务端(服务提供端): 提供远程方法的一端。
核心流程
名词拆解,答疑解惑
最基础的RPC框架-基石Socket
这是一个标准的RPC的原理网图,如果我们摘掉客户端 Stub和服务端 Skeleton,就成了用socket实现的一个普通通信代码,同时也是最简单的RPC框架。
java
---------------------------客户端---------------------------
// 1. 创建Socket连接服务端(假设服务端运行在本机8080端口)
Socket socket = new Socket("localhost", 8080);
// 2. 获取输出流并包装为对象流(发送请求)
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
// 3. 获取输入流并包装为对象流(接收响应)
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())
// 4. 发送方法名和参数(模拟调用add(1, 2))
oos.writeUTF("add"); // 写入方法名(UTF编码)
oos.writeInt(1); // 写入第一个参数
oos.writeInt(2); // 写入第二个参数
oos.flush(); // 确保数据发送
// 5. 读取服务端返回的结果
int result = ois.readInt();
System.out.println("调用结果: " + result);
---------------------------服务端---------------------------
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
String methodName = ois.readUTF(); // 读取方法名
int a = ois.readInt(); // 读取参数
int b = ois.readInt();
int result = a + b; // 实际计算
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeInt(result); // 返回结果
那么这个最基础的框架有什么问题呢?
- 与服务端的强绑定 :
- 服务端通过ois.readUTF()读取方法名,ois.readInt()读取参数,客户端必须严格按顺序写入对应类型数据。
- 若服务端期望更多参数(如String类型),客户端需追加oos.writeUTF("param")。
- 局限性:
- 固定方法名: 客户端硬编码了方法名"add",实际RPC框架会通过接口动态代理实现。
- 简单序列化: 使用Java原生对象流,跨语言支持差;实际框架会使用JSON/Protobuf等协议。
- 无异常处理: 未处理服务端方法不存在或参数错误的情况(需自定义协议约定错误码)。
- 资源浪费: Socket的频繁创建连接
为了解决这些问题,各种RPC框架出现了,比如Dubbo、Grpc等,他们的底层基础是Socket,因为要依赖Socket进行网络传输,所以是Socket的高度封装。在此之上通过动态代理、序列化、服务治理等功能,简化开发并提升系统能力,并且引入了一些高性能网络库(如Netty)、高效序列化协议(如Protobuf)和各种服务治理机制(如Zookeeper)
RPC和Http有什么关系?
先来对比一下两者的区别,但是这两者一般不能对比,因为RPC是一种设计理念,而Http是一种通信协议实现
场景 | 典型HTTP接口(如RESTful API) | RPC框架(如gRPC) |
---|---|---|
设计目标 | 资源操作(GET/POST/PUT/DELETE) | 方法调用(远程函数执行) |
数据格式 | 文本(JSON/XML) | 二进制(Protobuf/Thrift) |
调用方式 | 显式构造URL和参数 | 通过接口代理直接调用方法 |
协议灵活性 | 需遵循HTTP语义 | 可自定义协议(如Dubbo的私有协议) |
性能 | 较低(头部冗余,文本解析慢) | 较高(二进制压缩,协议精简) |
先来说一下Http和Socket的关系,有助于理解和RPC的关系
- HTTP :应用层协议 ,定义数据格式(请求头、请求体等)。HTTP基于TCP,HTTP协议依赖TCP建立连接(三次握手)和传输数据。
- TCP :传输层协议 ,提供可靠的数据传输。TCP基于Socket实现:操作系统通过Socket API暴露TCP能力,例如Java中的ServerSocket和Socket类。
- Socket :操作系统提供的编程接口,用于操作TCP/UDP协议。
Feign算不算RPC?
Feign这个框架,基本是HttpClient->RestTemplate->Feign(OpenFeign)这样的演化过程,实际上就是一个声明式、模板化的Http客户端 ,主要用于简化HTTP API的调用。
FeignFeign通过动态代理和接口封装隐藏了HTTP调用细节,符合RPC"透明调用"的核心特征,从广义上可以认为是RPC的一种实现,狭义上不属于传统RPC框架:
- 协议限制 :Feign强绑定HTTP协议,而传统RPC框架支持更灵活的协议。
- 设计理念差异:Feign围绕RESTful资源操作设计,而RPC框架以方法调用为中心。
- 性能与扩展性:Feign的性能和跨语言能力弱于专用RPC框架(如gRPC)。
为什么叫客户端Stub(存根)和服务端Skeleton(骨架)
客户端Stub和服务端Skeleton的概念最早源于1984年Andrew Birrell和Bruce Nelson的论文《Implementing Remote Procedure Calls》。
该论文提出将RPC抽象为五个组件(User、User-stub、RPCRuntime、Server-stub、Server),其中Stub和Skeleton的设计目标是屏蔽网络通信复杂性,是实现透明调用的基石。
简单来说,Stub 帮客户端假装自己在本地调用,Skeleton 帮服务端偷偷执行真实方法,深藏功与名~
如何写一个RPC框架?
偷个小懒,从Github上扒了一个RPC教学类star排名前几的项目,github.com/Snailclimb/...,接下来会根据该项目的代码进行讲解。项目启动很简单,下载一个3.5.10或者最新的zk,启动后,先启动example-server,再启动example-client即可。
大致讲一下代码结构,对应上前面讲的关键组件
- example-client: 客户端(服务消费端)
- example-server: 服务端Skeleton(骨架)
- hello-service-api: API拓展包,就是咱们组件管理里面给项目定义的各种export包,对外提供的资源(接口)
- rpc-framework-common、rpc-framework-simple: 客户端Stub(存根)、服务端Skeleton(骨架)、网络传输
代码分析
资源定义
需要注解+bean注册类
一般来说至少需要两种类,一种是服务端在类上的服务方定义@RpcService ,一种是客户端在类变量上定义的资源定义@RpcReference。这里多了一个扫描器@RpcScan,类似于@ComponentScan,有用处,但是不要也可以,可以直接在@RpcService中加上@Component之类的的bean资源注解,交给Spring,事后再捞出来已注册的bean特殊处理即可。
- CustomScannerRegistrar这个类用于将RpcService注解定义的资源塞入Spring的Bean容器中。
- SpringBeanPostProcessor implements BeanPostProcessor,这个类是核心注册类,用于定义当前项目的资源和变量的注入两件事情。
- 在postProcessBeforeInitialization中遍历已注册的bean,找到类上有注解RpcService的对象,定义服务端的资源,并调用服务治理(比如Zookeeper)进行了资源注册。
- 在postProcessAfterInitialization中遍历已注册的Bean,找到变量上有注解RpcReference的对象,创建客户端Stub(存根)反射注入该变量中。
使用BeanPostProcessor这个进行Bean生命周期拓展可以,但是更推荐使用LifeCycle这个Bean生命周期全部走完再进行操作的类,避免BeanPostProcessor后有什么额外的操作影响当前拓展。
服务治理
需要大致两种接口和一个本地缓存操作类
- ServiceRegistry---服务注册接口,方法是registerService,单纯一个资源注册即可,入参是资源名rpcServiceName和节点网络地址internetAddress
- ServiceDiscovery---服务发现接口,方法是lookupService,需要通过指定的参数获取对应的资源,才能知道调用具体哪一个节点
- ServiceProvider ---本地缓存操作类,方法提供void publishService(RpcServiceConfig rpcServiceConfig)和Object getService(String rpcServiceName)。这个类中会定义一个本地缓存,Map<String(rpcServiceName), Object(服务端对象)> serviceMap,在调用publishService的时候会调用ServiceRegistry.registerService,并往serviceMap新增一个资源KV。getService类就是服务端在处理请求时,根据rpcServiceName返回真正对象,从而反射处理请求,这是服务端Skeleton(骨架)的一部分。
网络传输
核心模块,需要定义出入参和信息传递三个类,定义两个接口,一个负责发消息,一个负责接收消息
- RpcRequest-包含请求方的信息(链路ID、版本号、分组等各种校验和日志信息)、入参的类型和值、接口名和方法名(用于代理对象反射调用方法)
- RpcResponse-包含方法调用结果、结果码、信息
- RpcMessage-用于网络传输,将RpcRequest和RpcResponse包住,增加消息类型(比如心跳检测、请求RpcRequest、返回结果RpcResponse)、编码方式、压缩方式、加密解密
收发消息这个需要定义发送端和服务端,这时候技术选型就是真正的传输了
传输方式 | 性能 | 复杂度 | 适用场景 | 代表框架 |
---|---|---|---|---|
原生Socket | 高 | 高 | 需要底层控制的场景 | Java RMI(究极老) |
Netty(主流) | 高 | 中 | 高并发生产环境 | Dubbo、gRPC |
HTTP/1.x | 中 | 低 | 兼容性优先的跨平台调用 | Spring Cloud Feign |
HTTP/2 | 高 | 中 | 现代高性能RPC | gRPC(默认) |
自定义协议 | ? | 高 | 内部高性能服务调用,定制化处理 | Dubbo协议 |
这里简单讲下代码里的Netty模块吧,毕竟是主流
客户端:
- ChannelProvider -用于存储和获取Channel对象,Channel是Netty框架中的一个核心接口,代表了一个网络连接,可以用于读写操作。内部用了一个ConcurrentHashMap来复用和管理这些Channel对象。
- NettyRpcClient -主要用于向服务端发送请求,会调用ChannelProvider创建和获取Channel。因为内部使用CompletableFuture做了异步,所以用UnprocessedRequests管理异步请求映射,UnprocessedRequests的生产在NettyRpcClient类,消费在NettyRpcClientHandler监听类。
- NettyRpcClientHandler -处理服务端返回的信息,比如请求的响应结果和心跳信息。
服务端:
- NettyRpcServer -完整初始化一个Netty的服务端,用于监听请求
- NettyRpcServerHandler -监听并校验消息,调用RpcRequestHandler类反射执行指定方法,并响应结果信息
- RpcRequestHandler-这个类要做的很简单,从RpcMessage解析出rpcServiceName,从本地缓存类ServiceProvider中获取到真正的类对象,invokeTargetMethod反射调用方法即可
流程图
拓展
额外插播一个我觉得很有意思的网站,云原生计算基金会(cncf)的技术全景图landscape.cncf.io/,里面都是一些很知名的分布式项目,涵盖容器、CI/CD、监控、服务治理、网络、分布式数据库、流处理计算、消息队列等。大家如果遇到什么问题,想要拓展思路或者单纯为了技术选型,都可以来瞅瞅,激发灵感。当然最重要的是看看目前最潮流的技术是啥样的,发展到什么程度了。
通信协议
通信协议是 RPC 通信双方约定的 数据传输规则,定义了如何组织、传输和解析数据包。其职责如下:
- 规范通信的消息格式(如头部、正文、校验码等)。
- 定义通信流程(如建立连接、心跳检测、超时重试等)。
- 处理网络传输细节(如 TCP/HTTP 层的行为、多路复用等)。
协议和序列化的区别,用寄快递举例来讲,协议定义收发快递的规范(哪里发快递,怎么发以及收快递,快递的单子信息)、走陆运还是空运海运(通信流程)。序列化就是单纯决定用盒子装还是用袋子装这个东西。
一次客户端发起的 RPC 调用的流程是,协议规范传输行为 → 序列化处理数据内容 → 协议封装并发送数据。
Dubbo2/3 | gRPC | Thrift | |
---|---|---|---|
传输协议 | TCP 长连接(Dubbo 协议),HTTP/2(Triple 协议,Dubbo 3.x 引入) | 基于 HTTP/2 | TCP、HTTP 等 |
支持序列化方式 | Hessian 2、Protobuf、JSON 等 | Protobuf、JSON 等 | TBinary(Thrift自研)、JSON、CompactProtocol 等 |
默认序列化方式 | Hessian 2(Dubbo 协议);Protobuf(Triple 协议) | Protobuf | TBinary |
跨语言支持能力 | 支持 Java 为主,对其他语言有一定支持,Triple 协议增强了跨语言能力 | 支持多语言,如 Java、C++、Python、Go 等 | 支持多语言,如 Java、C++、Python、PHP 等 |
社区活跃度 | Apache Dubbo 社区活跃度较高,有大量企业应用和贡献 | 社区活跃,有广泛的用户和贡献者 | 社区有一定活跃度,在特定领域应用较多 |
核心优势 | 高性能,长连接减少开销;可扩展性,协议头部预留扩展字段;可靠性,支持心跳检测、超时重试 | 基于 HTTP/2 和 Protobuf 的高性能;多语言跨平台二进制兼容能力 | 高效二进制协议,节省带宽和提高性能;多语言支持 |
服务治理能力 | Dubbo 框架具备完善的服务治理功能,如服务注册与发现、负载均衡、容错机制等 | 通过相关组件和工具可实现服务治理,如服务发现、负载均衡等 | 可通过扩展实现服务治理功能 |
流式通信 | Triple 协议支持流式调用,Dubbo 协议默认不支持 | 支持客户端流、服务器流和双向流 | 支持流式传输 |
序列化
通常我们需要提供一个Serializer接口用以扩展序列化方式,序列化byte[] serialize(Object obj)和反序列化 T deserialize(byte[] bytes, Class clazz);。以下是常见的序列化框架,简单搜罗网上数据做了个横向对比表格,一般Java环境推荐个人使用Kryo和JSON来适配不同环境。
框架 | 设计定位 | 序列化速度 | 数据体积 | 跨语言支持 | 兼容性 | 社区活跃度 | 易用性 | 典型适用场景 |
---|---|---|---|---|---|---|---|---|
Kryo | 纯Java高性能序列化 | 极快 | 最小 | 否(仅Java) | 低(字段增减需处理) | 高 | 需管理线程安全/类注册 | 实时通信、游戏、内存缓存 |
Protostuff | 基于Protobuf的无预编译Java优化版 | 快 | 小 | 有限(主Java) | 中(末尾追加兼容) | 低 | 无需预编译,但需处理无参构造 | 微服务通信、持久化存储 |
Protobuf | 跨语言结构化数据协议 | 较快 | 小 | 是 | 高(版本兼容) | 高 | 修改Schema需重新生成代码,需预编译.proto文件 | 多语言RPC、API协议、长期数据存储 |
Hessian/Hessian2 | 跨语言动态兼容协议 | 中等/较快(接近Protobuf) | 大/中等 | 是 | 高(自动兼容) | 低 | 开箱即用,无复杂配置 | 遗留系统集成、简单跨语言调用 |
JSON(Jackson/fastjson/gson) | 可读性优先的文本序列化(其他为二进制,均不可读!) | 较慢 | 最大(文本) | 是 | 高(无结构约束) | 高 | 直接操作POJO,开发便捷 | 前后端交互、调试日志 |
Java原生 | 内置序列化 | 极慢 | 极大 | 否(仅Java) | 低(完全依赖类结构) | 低 | 简单但效率低下 | 临时测试、简单场景 |
MessagePack | 高效二进制跨语言序列化 | 快 | 小(紧凑) | 是 | 中(字段顺序敏感) | 中等活跃(持续更新) | 类似JSON,需处理二进制 需POJO注解或预定义类 | 移动端、IoT设备、跨语言微服务 |
压缩
序列化方式合适的话会有一定的压缩率,但是我们仍然需要提供扩展接口Compress,压缩byte[] compress(byte[] bytes)和解压缩byte[] decompress(byte[] bytes)。比较常见的是GZIP,毕竟JDK内置确实方便好用,最好和序列化的一起打配合。
框架 | 设计定位 | 压缩速度 | 压缩率 | 解压速度 | 内存占用 | 兼容性 | 易用性 | 适用场景 | 社区活跃度 |
---|---|---|---|---|---|---|---|---|---|
GZIP | 高压缩比,长期存储 | 慢 | 高 | 中等 | 中 | 跨平台(RFC 1952) | JDK原生支持,简单API | HTTP压缩、日志归档 | 稳定维护(JDK内置) |
LZ4 | 极致速度优先,低延迟场景 | 极快 | 低 | 极快 | 低 | 需第三方库 | 依赖lz4-java 库,API简洁 |
实时通信、内存缓存、游戏数据流 | 高活跃度(GitHub开源) |
Snappy | 速度与压缩率平衡,Google生态集成 | 快 | 中低 | 快 | 低 | 跨语言(C++/Java) | 简单API(如Snappy.compress() ) |
大数据传输(Hadoop、Kafka) | 高活跃度(Google维护) |
Bzip2 | 高压缩比,牺牲速度 | 极慢 | 最高 | 慢 | 高 | 需第三方库 | 依赖commons-compress 库 |
离线数据归档、静态资源压缩 | 低活跃度(维护较少) |
LZO | 快速解压,适合读取密集型场景 | 快(解压) | 低 | 极快 | 低 | 需第三方库 | 复杂配置(需指定算法版本) | 日志分析、HDFS存储 | 停滞(社区支持有限) |
LZMA/LZMA2 | 极高压缩比,适合长期归档 | 极慢 | 最高 | 中等 | 高(百MB级) | 需第三方库 | 配置复杂(需设置字典大小等参数) | 软件分发、历史数据归档 | 中等(7z生态维护) |
QuickLZ | 极速压缩,实时性优先 | 极快 | 中低 | 极快 | 低(KB级) | 社区支持差,自己写 | 文档少,自己写 | 实时通信、嵌入式设备、数据库写入 | 低活跃度(更新停滞) |
负载均衡
分布式部署的话肯定需要一个合理的负载均衡算法,这里也需要提供一个扩展接口。JSF里面还蛮全的,可以参考源码中的com.jd.jsf.gd.lb.Loadbalance,去找各个实现类,这里留一个JSF官方文档的表格,我觉得总结的还蛮好的。
名称 | 配置 | 版本 | 优点 | 缺点 |
---|---|---|---|---|
随机(默认) | random | 1.0.0 | 1. 按权重的进行随机,可以方便的调整 2. 调用量越大分布越均匀 | 当调用次数比较少时,Random 产生的随机数可能会比较集中,此时多数请求会落到同一台服务器上。 这个缺点并不是很严重,多数情况下可以忽略。RandomLoadBalance 是一个简单,高效的负载均衡实现,因此它被作为缺省实现。 |
轮询 | roundrobin | 1.0.0 | 1. 轮循,按公约后的权重设置轮循比率 | 如果某个Provider较慢,可能会积压请求 |
最少并发优先 | leastactive | 1.0.0 | 1. 在调用前后增加计数器,并发最小的Provider认为是最快的Provider并发大的Provider认为是慢的Provider。 2. 并发数相同再比较权重。 3. 这样快的Provider收到更多请求。 | 快速抛异常的Provider有可能被误认为是响应快的Provider。 目前此问题已解决,Provider抛异常会降低选中概率,最低到10%,等Provider恢复并调用成功后,选中概率回恢复到100%。 |
一致性hash | consistenthash | 1.2.0 | 1. 服务端节点没变化的情况下,同样的请求(根据第一个参数值进行hash)会指向同一台机器 2. 基于虚拟节点,如果一台挂了也没事,会平摊到其它提供者 | 可能调用分布不均匀。 |
本机IP调用优先 | localpref | 1.5.1 | 1. 本机IP上的Provider优先调用 2. 匹配到多个使用随机调用本机IP,未匹配也随机调用远程机器 | 计算相同IP稍微增加点耗时。 |
最快响应 | shortestresponse | 1.7.2 | 根据平均响应时间 * 当前请求数,选取两者乘积最小的节点 | cpu偏高 |
平滑加权轮询 | swrr | 1.7.7 | 平滑加权轮询算法,provider接受到的流量更加均匀 | 计算会消耗些cpu |
自定义负载 | Extensible里的value | 1.7.2 | 自定义负载均衡算法,具体例子如下面 |
动态代理
随着JDK的版本更新JDK动态代理的效率越来越高了,Spring默认代理接口也是用的这个。在查询资料的时候发现之前没怎么了解的Byte Buddy,神灯冲浪的时候发现之前有人了解过,ASM字节码操作类库(打开java语言世界通往字节码世界的大门)
框架 | 实现原理 | 依赖 | 代理限制 | 性能 | 易用性 | 内存开销 | 适用场景 |
---|---|---|---|---|---|---|---|
JDK动态代理 | 基于接口,通过反射生成代理类 | JDK原生支持 | 仅支持接口 | -生成速度:快(4 ms) -调用速度:反射较慢(约136 ms/千万次调用) | 简单(仅需实现InvocationHandler 接口) |
低(代理类由JVM缓存) | 接口代理(如Spring AOP默认策略) |
CGLIB | 基于继承,通过字节码增强生成目标类的子类 | 需引入cglib 库 |
可代理普通类(无法代理final 类和方法) |
-生成速度:较慢(108 ms) -调用速度:较快(约215 ms/千万次) | 中等(需配置Enhancer 和MethodInterceptor ) |
较高(生成子类字节码) | 类代理(如Spring无接口时的AOP) |
Javassist | 基于源码或字节码操作,通过字符串拼接生成类和方法 | 需引入javassist 库 |
可代理普通类(需处理构造函数,支持动态生成新类) | -生成速度:中(39 ms) -调用速度:慢(1690 ms/千万次) | 高(通过字符串拼接生成代码,无需了解字节码) | 中等(支持动态生成和缓存) | 快速原型开发、动态生成类(如ORM工具) |
ASM | 直接操作JVM字节码指令,基于访问者模式生成类 | 需引入asm 库 |
可代理任何类(包括final 类,但无法覆盖final 方法) |
-生成速度:极快(4 ms) -调用速度:最快(172 ms/千万次) | 低(需熟悉JVM指令和类结构) | 低(直接生成紧凑字节码) | 高性能场景(如序列化框架Kryo) |
Byte Buddy | 基于ASM的高层抽象,提供类型安全的DSL生成字节码 | 需引入byte-buddy 依赖 |
可代理任何类(支持复杂字节码操作) | -生成速度:快(接近ASM) -调用速度:接近ASM(179 ms/千万次) | 中等(需学习DSL语法,但比ASM友好) | 低(优化内存管理) | 复杂字节码操作(如Mockito、Hibernate) |
写在最后
这篇写在今年的二月底吧,当时是计划去写定制化开发的RPC的,结果想了想还是应该从源头开始,顺道我也相对系统的建立起RPC的知识体系。本文相对来说已经非常详细了,当时本来打算是四部曲,原理、实战、公司框架分析和主流RPC对比。结果写原理也就是本篇写了一半,公司给推了一篇大佬写的公司框架分析,好家伙写的真好,瞬间就觉得没必要重复写了,实战篇在后面马上出,主流RPC对比不一定会写的,但是我比较想看看GRPC,如果有机会的话会拜读一下。emmm,这半年的总结,躺了这么久也该回归了,虽然还要和生活搏斗,但是老本行不能忘了。最近在做一个还算有点意思的东西,写了一段有点意思的代码,应该下个月就会分享出来。工作上有意思的东西还不算特别多,之前写了太多东西,其实新的环境也有点雷同,公司的东西封装的太狠,说实话我也没有细细品味,仅仅是在用着,不过文档都写得蛮不错的。对了,实战篇的时候,我参杂着公司的RPC文档来聊些私货吧,有些设计思路我觉得可以参考,毕竟中间件大部分都是差不多的思路。
聊聊生活吧,又是深夜,沟槽的世界,到家10点,感觉还没干啥就0点多了,能不能给单身狗留一点追逐爱情的时间(大雾)。最近也是感受到了父母急切的心情,博主快28了,谁能想到还是一个纯情大男孩,淦,品味着男女对立的资讯,抱着吃屎的决心还要冲锋,这就是生活,一个巨大的SM场。当然我觉得我是看清了情感,所以才能更好地去追逐爱情,不过北京的相亲市场属实地狱,说是相亲其实也就扩列,我还是相对喜欢这种日久生情的感觉。但,哈哈,有时候就感觉男女沟通确实是门学问,大家都得学,还有思想独立拎得清的女生确实不多。以上就是我的一些简单吐槽,说是吃屎,但是我觉得这是我对期待生活和伴侣需要付出的代价吧,向阳而生吧,少年!