dubbo中集合变量非透明传输,序列化开销取决于元素内容、嵌套深度及协议选择;hessian2冗余高,kryo/protobuf更高效,优化需扁平dto、类注册与分页传输。

集合变量在 Dubbo RPC 调用中不是“透明传输”的,它的序列化开销会显著放大——尤其当嵌套深、元素多、类型杂时。关键不在“是不是集合”,而在于**集合里装了什么、怎么装、用什么序列化协议来处理它**。
集合本身不占大头,但泛型和实际元素决定成本
Java 中的 List<user></user>、Map<string orderdetail></string> 等集合类型,在序列化时会被展开为元素序列。开销主要来自:
-
类型信息冗余:Hessian2 会对每个元素重复写入类名、字段名(即使同类型),List 有 1000 个 User,就可能多写 1000 次
com.example.User字符串 -
泛型擦除后反推困难:运行时泛型已丢失,反序列化需依赖序列化器的元数据机制或白名单注册;Kryo/FST 若未提前注册
User类,会 fallback 到低效反射路径 -
空值与稀疏结构放大体积:ArrayList 中大量
null元素仍被编码;HashMap 的扩容桶、链表指针等内部结构不参与传输,但 key/value 序列化逻辑更复杂
不同序列化协议对集合的实际表现差异明显
同样传一个含 500 个 User 对象的 ArrayList,在 2.7.x + JDK 17 环境下实测典型开销(平均值):
- Hessian2(默认):序列化耗时 ~8.2ms,字节长度 ~410KB —— 字段名重复写入多,适合小集合、跨语言场景
- Kryo(注册类后):耗时 ~2.1ms,字节长度 ~260KB —— 无类名字符串,靠整数 ID 标识类型,但要求 Provider/Consumer 类注册一致
- Protobuf(Triple 协议):耗时 ~1.4ms,字节长度 ~190KB —— 需预定义 .proto,集合映射为 repeated 字段,零拷贝友好,但 Java 侧需生成类或使用 wrapper
- Fastjson2(JSON):耗时 ~11.6ms,字节长度 ~580KB —— 文本冗余高,可读性强,调试方便,但 CPU 和带宽双吃紧
高频踩坑点:集合引发的隐性问题
很多参数丢失、反序列化失败、CPU 尖刺,并非集合本身有问题,而是使用方式触发了协议限制:
-
父类/子类字段重名:如
BaseEntity.id和User.id同时存在,Hessian2 可能只保留后者值,导致 id 丢失(你知识库中提到的 case) - 未实现 Serializable 或无参构造:JDK/Kryo/FST 均要求目标类有 public 无参构造;Hessian2 宽松些,但缺构造仍可能静默跳过字段
-
集合内含不可序列化对象:比如
List<thread></thread>、Map, FileOutputStream>,运行时报NotSerializableException或直接 NPE -
递归引用未处理:A 包含 List,B 又引用回 A —— Hessian2 默认不支持循环引用,Kryo 需开启
setReferences(true)
优化建议:轻量、确定、可控
不追求“绝对最快”,而要让集合传输稳定、可预期、易排查:
- 接口参数尽量用 扁平 DTO,避免多层嵌套集合(如
Map<string list object>>></string>) - 生产环境优先选 Kryo(配合类注册)或 Protobuf(Triple 协议),避开 Hessian2 的隐式行为
- 对超大集合(>1000 元素),考虑分页传输或改用流式接口(Dubbo3 支持 StreamObserver)
- 在 Consumer 端加一层封装:用
Response<list>></list>统一包装,避免裸 List 作为方法返回值,便于后续加 traceId、分页元数据等 - 启用序列化监控:通过 Dubbo 的
MetricsFilter或自定义Filter记录单次调用中序列化耗时与字节数,建立基线










