一次诡异的RPC超时:Protostuff序列化踩坑
2025-7-17
| 2025-12-11
字数 1704阅读时长 5 分钟
password
icon
AI summary
type
status
date
slug
summary
tags
category
写 RPC 框架的时候,为了追求极致性能,我果断抛弃了 JDK 自带的序列化,选了 Protostuff。大家都说它快、体积小,是 RPC 界的宠儿。
然而,高性能的背后往往藏着一些不起眼的“坑”。最近在调试客户端调用时,遇到一个极其诡异的超时问题。这篇文记录一下从一脸懵逼到真相大白的全过程,希望能帮大家避避雷。

一、 现场还原:换个姿势居然就行了?

事情是这样的,我的 RPC 客户端在调用服务端接口时,配置一切正常,ZooKeeper 也能连上,但就是死活拿不到结果,最后只能抛出 TimeoutException
代码长这样:
我也试过打断点,客户端请求确实发出去了。这就很僵硬了,到底是网络问题,还是服务端挂了?
为了排除法,我随手改了一个配置:把序列化方式从 protostuff 换回了最原始的 jdk
结果……竟然通了! 调用成功,丝般顺滑。
这一刻我大概心里有底了:网络没锅,ZK 没锅,代理逻辑也没锅。这绝对是 Protostuff 搞的事情。

二、 案情推理:服务端“沉默”的真相

既然换 JDK 序列化能通,说明业务逻辑没问题。那么问题大概率发生在这个环节:
  1. 客户端用 Protostuff 把 RpcRequest 变成了字节数组,发出去了。
  1. 服务端收到了字节数组。
  1. 关键点来了: 服务端试图用 Protostuff 把这堆字节还原成 RpcRequest 对象时,炸了。
但是,为什么客户端收到的是超时(Timeout)而不是报错?
因为反序列化通常发生在 IO 线程或者解码阶段,如果这里抛出了异常(比如空指针、类型转换错误)且没有被很好的捕获并封装成 RPC 响应返回给客户端,服务端处理线程就会直接中断。
这就导致服务端“沉默”了,既不回成功,也不回失败。客户端在那傻傻地等,直到耐心耗尽,抛出超时异常。

三、 深挖根源:Protostuff 的“泛型失忆症”

顺着这个思路,我去扒了一下 Protostuff 的文档和相关 Issue,发现这货对数组和集合的泛型处理由于性能优化的原因,其实是有缺陷的。
看看我的 RpcRequest 对象定义:
这种“模糊类型”时,会患上“失忆症”:
  • 序列化时: 它是看着具体的对象操作的(比如它知道这个 Object 其实是个 String),能写成字节。
  • 反序列化时: 它对着一堆字节,只知道这就该是个 Object。但它不知道这堆字节原本是 String 还是 Integer。元数据丢了,无法还原,直接报错。
而 JDK 序列化之所以慢,就是因为它把所有的类元信息都塞进去了,所以它没事。

四、 解决方案:给它套个壳(Wrapper 模式)

既然 Protostuff 记不住泛型里的具体类型,那我们就手动帮它记。最简单的办法就是Wrapper(包装)模式
我们不直接序列化 RpcRequest,而是把它包在一个有明确泛型定义的类里面。

1. 造个壳子 ProtostuffWrapper

弄个简单的泛型类,相当于给数据穿个马甲:

2. 改造序列化逻辑

ProtostuffSerialization 类里,稍微做个拦截。如果是 RpcRequestRpcResponse,就先套上壳子再序列化;反序列化时,先解开壳子。

五、 最后的翻车:由于没执行 mvn install 引发的惨案

代码改完,我觉得稳了,兴冲冲地重启服务端,运行客户端。
结果……还是超时?!
那一瞬间我都要怀疑人生了。逻辑天衣无缝,为什么不行?难道我对于 Protostuff 的理解全是错的?
排查了半天,最后发现了一个极其低级、但也是大家最容易犯的错误:
我只改了 serialization 模块的代码,但是没重新安装到本地仓库!
服务端项目依赖的是本地 Maven 仓库里的 jar 包,而我刚才改的代码还在 IDEA 的源码里,根本没更新到 jar 包里。服务端跑的还是旧代码!
执行完这句,重启服务端,再次调用。控制台终于打印出了久违的 Result。搞定!

碎碎念

这次排坑虽然花了一下午,但教训挺深刻:
  1. 控制变量法永远的神: 遇到疑难杂症,先把复杂的组件换成简单的(比如 Protostuff 换 JDK),能瞬间定位问题域。
  1. 高性能的代价: Protostuff 确实快,但它对 Java 语言特性的支持不如 JDK 完整,用的时候得清楚它的脾气(比如怕 Object 数组)。
  1. Maven 的锅: 只要涉及多模块开发,改了底层代码觉得没生效,先别急着改代码,先 clean install 一下,能省一半的头发。
  • rpc
  • protostuff
  • 编程角度理解黑格尔《法哲学原理》从“炼丹”到“建厂”:为什么说上下文工程(Context Engineering)才是AI应用的未来?
    Loading...