RPC 的主要流程
- 客户端 获取到 UserService 接口的 Refer: userServiceRefer
- 客户端 调用 userServiceRefer.verifyUser(email, pwd)
- 客户端 获取到 请求方法 和 请求数据
- 客户端 把 请求方法 和 请求数据 序列化为 传输数据
- 进行网络传输
- 服务端 获取到 传输数据
- 服务端 反序列化获取到 请求方法 和 请求数据
- 服务端 获取到 UserService 的 Invoker: userServiceInvoker
- 服务端 userServiceInvoker 调用 userServiceImpl.verifyUser(email, pwd) 获取到 响应结果
- 服务端 把 响应结果 序列化为 传输数据
- 进行网络传输
- 客户端 接收到 传输数据
- 客户端 反序列化获取到 响应结果
- 客户端 userServiceRefer.verifyUser(email, pwd) 返回 响应结果
整个流程中对性能影响比较大的环节有:序列化[4, 7, 10, 13],方法调用[2, 3, 8, 9, 14],网络传输[5, 6, 11, 12]。本文后续内容将着重介绍这3个部分。
序列化方案
Java 世界最常用的几款高性能序列化方案有 Kryo Protostuff FST Jackson Fastjson。只需要进行一次 Benchmark,然后从这5种序列化方案中选出性能最高的那个就行了。DSL-JSON 使用起来过于繁琐,不在考虑之列。Colfer Protocol Thrift 因为必须预先定义描述文件,使用起来太麻烦,所以不在考虑之列。至于 Java 自带的序列化方案,早就因为性能问题被大家所抛弃,所以也不考虑。下面的表格列出了在考虑之列的5种序列化方案的性能。
User 序列化+反序列化 性能
protostuff | 1654 | 240 |
kryo | 1288 | 296 |
fst | 1101 | 263 |
jackson | 959 | 385 |
fastjson | 603 | 378 |
kryo | 143 | 2080 |
fst | 118 | 3495 |
protostuff | 98 | 3920 |
jackson | 71 | 5711 |
fastjson | 40 | 5606 |
从这个 benchmark 中可以得出明确的结论:二进制协议的 protostuff kryo fst 要比文本协议的 jackson fastjson 有明显优势;文本协议中,jackson(开启了afterburner) 要比 fastjson 有明显的优势。
无法确定的是:3个二进制协议到底哪个更好一些,毕竟 速度 和 size 对于 RPC 都很重要。直观上 kryo 或许是最佳选择,而且 kryo 也广受各大型系统的青睐。不过最终还是决定把这3个类库都留作备选,通过集成传输模块后的 Benchmark 来决定选用哪个。
protostuff | 103.92 | 89.50 | 83.33 | 21.17 |
kryo | 99.23 | 76.71 | 73.89 | 25.68 |
fst | 102.33 | 76.24 | 78.81 | 23.30 |
最终的结果也还是各有千秋难以抉择,所以 Turbo 保留了 protostuff 和 kryo 的实现,并允许用户自行替换为自己的实现。