Skip to the content.

缓存与 HTTP JSON 配置复用

问题与原因

缓存读取已经从后端拿到完整的 JSON byte[],原实现仍为每次读取创建 MemoryStream、JsonSerializerOptions 和 Unicode 编码器。缓存写入与 HTTP JSON 读取也在每次调用时创建相同的配置。配置承载固定的格式规则和类型元数据缓存;反复创建会让同一类型反复准备元数据,并增加托管堆分配。

缓存与 HTTP 的规则不同:缓存读取区分属性名大小写,HTTP 读取不区分大小写;缓存遇到无效 JSON 抛出异常,HTTP 辅助方法返回默认值。因此两者各自持有私有配置,不共用一份全局配置。

本次方案

两个辅助类各保留一份 static readonly JsonSerializerOptions。缓存读取直接解析返回的字节,避免再包装 MemoryStream;HTTP 仍从响应流异步解析。配置初始化后不修改,多个调用只复用配置与类型元数据,不共享业务数据。

直接解析字节与原流解析有两个边界差异:流解析接受开头的 UTF-8 BOM,Span 解析前需跳过该前缀;Span 解析没有取消参数,因此缓存读取在解析前后检查取消。缓存字节既不复制也不修改,BOM 不在开头时仍视为无效 JSON。

这次保留全部公开方法签名和原有 JSON 规则。JsonSerializerOptions 的复用不会消除现有 JSON API 的运行时类型发现;如要支持 Native AOT,还需单独迁移调用方提供的静态元数据。

测量与验证

原性能切片 52abd2d 在 .NET 10.0.12、Debug、内存后端和小型 DTO 下预热 32 次,再测量 64 次,记录了以下单次分配。数字只代表该条件下的 JSON 辅助路径,不代表网络吞吐量。

路径 修改前 修改后
缓存读取 45,809 B/次 400 B/次
缓存写入 49,825 B/次 576 B/次
HTTP JSON 读取 48,043 B/次 2,576 B/次

本次在当前 master 工作区以 .NET 10.0.12、Release 重跑同一预热与采样方式,修改后分别为缓存读取 312 B/次、缓存写入 464 B/次、HTTP JSON 读取 2,304 B/次。Release 结果没有对应的同条件修改前测量,因此不与上表的 Debug 数字直接相减。

JsonReuseRegressionTests 检查预热后的分配,并覆盖属性名大小写、中文转义、null、枚举、嵌套值、缓存失效行为、无效 JSON、取消、BOM 与并发调用。分配断言留有余量,避免把某个运行时版本的精确字节数当作契约。