文章目录
-
- 一、先分清楚:到底是什么导致了问题?
-
- [1. 消息或请求大小超过限制](#1. 消息或请求大小超过限制)
- [2. 数据传输和解析开销增加](#2. 数据传输和解析开销增加)
- [3. 同步等待时间过长](#3. 同步等待时间过长)
- [二、方案一:大数据放进 Redis,通道只传任务标识](#二、方案一:大数据放进 Redis,通道只传任务标识)
-
- [1. 整体交互流程](#1. 整体交互流程)
- [2. 通知报文示例](#2. 通知报文示例)
- [3. 这样做的好处是什么?](#3. 这样做的好处是什么?)
- [4. 放进 Redis,并没有消除网络传输](#4. 放进 Redis,并没有消除网络传输)
- [5. Redis 也有适用边界](#5. Redis 也有适用边界)
- 三、要让外置数据方案可靠,需要补上哪些设计?
-
- [1. 先写入数据,再发送任务通知](#1. 先写入数据,再发送任务通知)
- [2. 使用任务标识实现幂等](#2. 使用任务标识实现幂等)
- [3. 合理设置有效期](#3. 合理设置有效期)
- [4. 回调与查询相互补充](#4. 回调与查询相互补充)
- [5. 保留协议与数据校验信息](#5. 保留协议与数据校验信息)
- [四、方案二:GZIP 压缩后,通过原通道传输](#四、方案二:GZIP 压缩后,通过原通道传输)
-
- [1. GZIP 与 Base64 的作用不同](#1. GZIP 与 Base64 的作用不同)
- [2. 压缩解决什么问题?](#2. 压缩解决什么问题?)
- [3. 压缩带来的成本](#3. 压缩带来的成本)
- [4. 压缩不能替代异步处理](#4. 压缩不能替代异步处理)
- 五、两种方案应该如何选择?
- 六、几个容易混淆的认识
- 七、结语
两个系统对接时,最开始的交互通常很简单:系统 A 组装 JSON,通过 HTTP 或消息队列发送给系统 B,系统 B 完成处理后返回结果。
随着数据规模增加,一次请求可能包含大量明细、规则和参数,返回结果也可能很大。原本简单的接口开始出现请求耗时增长、消息超过大小限制、调用超时、重复重试等问题。
这时有两种常见处理思路:
- 将大数据保存到 Redis 等共享存储,HTTP 或 MQ 只传任务标识和数据引用。
- 对原始 JSON 进行 GZIP 压缩,再通过原有通道传输。
本文用"系统 A"和"系统 B"说明这两种方案。重点是交互方式和设计取舍,不依赖具体业务。
一、先分清楚:到底是什么导致了问题?
假设系统 A 需要向系统 B 提交一份包含几十万条明细的计算请求。
最直接的实现如下:
text
系统 A
└─ 组装大 JSON
└─ HTTP / MQ 传输完整数据
└─ 系统 B 解析、计算
└─ 返回大结果
这个过程可能遇到三类不同的问题。
1. 消息或请求大小超过限制
HTTP 网关、反向代理、应用服务器、MQ Broker 和客户端都可能存在大小限制,具体限制取决于产品与配置。
即使某个环节允许大消息,也不意味着链路上的其他环节都能接受。
2. 数据传输和解析开销增加
报文越大,通常需要越多网络带宽,同时增加序列化、反序列化和内存分配开销。
消息发送失败后,如果每次都重新发送完整数据,重试成本也会增加。
3. 同步等待时间过长
如果系统 A 发出 HTTP 请求后,一直等待系统 B 完成计算,那么等待时间不仅包括网络传输,还包括排队、解析、计算和结果生成。
即使输入数据不大,计算时间很长,也可能发生超时。
因此,需要区分两个问题:
报文太大,属于数据传输问题;处理太慢,属于任务执行与等待方式问题。
压缩主要处理前者,异步任务交互主要处理后者。两者可以结合使用。
二、方案一:大数据放进 Redis,通道只传任务标识
这个方案将"数据保存"和"任务通知"拆开。
大 JSON 不再放进通知报文,而是提前保存到双方可访问的 Redis。通知中只携带任务标识、输入位置等少量信息。
1. 整体交互流程
text
① 系统 A 组装输入数据
↓
② 系统 A 将输入写入共享 Redis
task:{taskId}:input
↓
③ 系统 A 通过 HTTP 或 MQ 通知系统 B
"请处理这个 taskId,输入位于指定位置"
↓
④ 系统 B 受理任务并异步读取输入、执行计算
↓
⑤ 系统 B 将结果写入 Redis
task:{taskId}:output
↓
⑥ 系统 B 通过回调或消息通知系统 A
"任务已完成,结果位于指定位置"
↓
⑦ 系统 A 读取结果并更新任务状态
↓
⑧ 页面轮询或手动刷新,展示结果或失败原因
如果使用 HTTP 受理任务,接口应在完成必要校验和任务登记后尽快返回,不必等待实际计算结束。任务受理成功和任务计算成功是两个不同状态。
2. 通知报文示例
以下是示意结构,字段名称可按双方协议约定:
json
{
"taskId": "task-20261026-001",
"inputKey": "task:{task-20261026-001}:input",
"schemaVersion": "1",
"compression": "none"
}
完成通知也保持较小体积:
json
{
"taskId": "task-20261026-001",
"status": "SUCCEEDED",
"outputKey": "task:{task-20261026-001}:output"
}
HTTP 或 MQ 承担的是任务通知职责,大数据通过共享存储交换。
3. 这样做的好处是什么?
首先,通知报文变小,可以避开原有通知通道承载大消息时的大小限制和高重试成本。
其次,任务受理和业务计算分开,系统 A 不需要维持一个长时间等待的 HTTP 请求,减少计算耗时引发的同步超时。
再次,只要输入仍然有效,通知失败时可以重发任务标识,不必随通知重复发送整份 JSON。任务标识也便于串联日志、状态查询、回调和结果读取。
4. 放进 Redis,并没有消除网络传输
这是一个容易被误解的地方。
原来的数据路径可能是:
text
系统 A → 系统 B
引入 Redis 后变成:
text
系统 A → Redis → 系统 B
数据仍然需要传输,甚至可能增加链路上的累计传输量和读写次数。
因此,这个方案的价值是将大数据移出通知通道,并改变任务等待方式,而不是保证总体网络流量一定更少。
如果瓶颈本身就是共享网络带宽,或者大 JSON 的解析成本,那么仅仅增加 Redis 不一定能改善性能。
5. Redis 也有适用边界
Redis 适合有明确生命周期、读写频繁、需要较快访问的临时数据交换场景,但不宜因为"读写快"就无限制地保存大对象。
如果输入或结果很大、需要长期保留,或者任务可能积压很久,可以考虑对象存储或文件存储:
text
任务状态与索引 → 数据库 / Redis
大型输入与结果 → 对象存储 / 文件存储
任务通知 → HTTP / MQ
"大数据外置、通知传引用"是交互思路,Redis 只是其中一种存储选择。
三、要让外置数据方案可靠,需要补上哪些设计?
1. 先写入数据,再发送任务通知
应先完成输入数据写入,再通知系统 B。否则系统 B 收到任务时,可能找不到完整输入。
Redis 写入成功与 HTTP 或 MQ 通知成功之间,通常没有天然的跨系统原子性,需要考虑失败补偿:
- 输入已写入,但通知失败:保留任务记录,支持重试或补发。
- 通知已送达,但系统 A 没有收到响应:使用同一个任务标识重试,避免重复计算。
对于要求可靠投递的场景,可以结合持久化任务表、待发送记录和补偿扫描。
2. 使用任务标识实现幂等
同一任务可能因为网络抖动、消息重投或回调重试而被重复通知。
系统 B 应根据任务标识识别:任务是否已受理、是否正在执行、是否已经完成。重复通知不应简单地触发重复计算。
3. 合理设置有效期
输入数据的有效期需要覆盖排队、处理、重试和结果确认时间,不能只按"正常计算耗时"设置。
结果也应保留到消费方完成读取和业务确认。不同阶段可以采用不同保留时间,并在任务超时后做清理。
同时需要考虑 Redis 的淘汰策略和故障恢复方式。对不能丢失的任务,不应仅依赖可能过期或被淘汰的缓存记录。
4. 回调与查询相互补充
回调可能失败,也可能重复到达。
系统 B 应在结果写入成功后发送完成通知;系统 A 则应支持幂等处理回调,并通过任务状态查询或补偿扫描恢复遗漏状态。
页面只负责展示状态,不应承担后台任务可靠性的保证。
5. 保留协议与数据校验信息
建议双方约定数据结构版本、压缩方式、输入大小和必要的校验信息,并限制可访问的数据位置。
接收方应检查任务与数据引用是否匹配,避免将外部传入的任意 key 或地址直接当作可信输入读取。
四、方案二:GZIP 压缩后,通过原通道传输
如果 JSON 主要包含大量重复字段和结构化明细,GZIP 往往能够减小数据体积。但实际效果需要用真实报文测量,不能假定固定压缩比例。
整体流程如下:
text
发送方:
JSON → UTF-8字节 → GZIP压缩 → MQ发送
接收方:
MQ接收 → GZIP解压 → UTF-8解码 → JSON解析
如果传输协议只允许文本,可以增加 Base64:
text
发送方:
JSON → GZIP压缩 → Base64编码 → MQ发送
接收方:
MQ接收 → Base64解码 → GZIP解压 → JSON解析
1. GZIP 与 Base64 的作用不同
GZIP 用于压缩数据,Base64 用于把二进制转换成文本。
Base64 本身不会压缩数据,编码后的体积通常比压缩字节增加约三分之一,另外还可能存在报文封装开销。
例如,假设某份 JSON 原始大小为 12 MB,实测 GZIP 后为 2 MB,那么 Base64 后大约为 2.67 MB。这只是算术示例,不代表任何 JSON 都能达到这个压缩率。
如果 MQ 和客户端支持二进制消息体,可以直接发送压缩字节,通常不必再做 Base64。
2. 压缩解决什么问题?
它可以减小经过通信通道的数据量,降低带宽占用,并帮助报文满足消息大小限制。
但限制是否满足,应按目标系统实际校验的大小计算。如果接收端会解压并检查解压后体积,或者通道还存在额外封装,不能只看压缩结果。
3. 压缩带来的成本
压缩和解压需要 CPU,数据处理也会占用内存。
如果实现方式在内存中同时保存原始字符串、字节数组、压缩数据和 Base64 字符串,峰值内存可能明显高于单份报文大小。
对于较大的数据,可以评估流式压缩和解析,并对解压后的大小设置上限。
4. 压缩不能替代异步处理
即使消息已经压缩得很小,系统 B 仍然可能需要较长时间才能完成业务计算。
如果系统 A 继续同步等待完整结果,仍然可能超时。因此,压缩和异步任务交互是两个可以独立采用的设计。
五、两种方案应该如何选择?
| 维度 | 数据外置,只传引用 | 压缩后传完整内容 |
|---|---|---|
| 主要改变 | 数据与通知的传递方式 | 报文编码和体积 |
| 适合场景 | 数据较大,任务耗时长,需要重试和查询 | 压缩后能稳定满足通道限制,希望保留现有交互 |
| 通知或消息大小 | 较小 | 取决于压缩结果 |
| 总体传输量 | 不一定减少 | 对可压缩数据通常能够减少 |
| 额外依赖 | 共享存储及生命周期管理 | 双方压缩、解压协议 |
| 主要成本 | 共享存储读写、容量、状态与补偿设计 | CPU、内存、兼容性处理 |
| 对计算超时的影响 | 配合异步受理可减少同步等待 | 不能单独解决长时间计算 |
选择前,可以先测量真实数据:原始大小、压缩后大小、序列化耗时、传输耗时、处理耗时、并发量和峰值内存。
如果压缩后体积可控,现有通道也能稳定处理,压缩方案通常改动较小。
如果单次数据很大、计算时间长、需要多次读取或独立管理输入与结果,那么将数据外置并采用异步任务交互更有价值。
两种方案也可以组合:
text
系统 A:JSON → GZIP压缩 → 写入共享存储
↓
发送任务标识和数据引用
↓
系统 B:读取压缩数据 → 解压 → 处理 → 保存结果 → 通知完成
组合使用时,仍然需要测量压缩收益是否足以覆盖新增的 CPU 和内存成本。
六、几个容易混淆的认识
"放进 Redis 就不会超时了。"
Redis 读写和数据解析仍然可能超时。异步受理减少的是对整个计算过程的同步等待,不代表每个步骤都没有超时风险。
"Redis 比 HTTP 快,所以所有大报文都应该放进 Redis。"
这里比较的是不同的交互路径,不能只用单次存储访问延迟判断。应看端到端耗时、容量、可靠性和维护成本。
"Base64 可以进一步压缩。"
Base64 是编码,会增加体积。是否需要它取决于通道是否支持二进制。
"MQ 已经异步,计算再久都没关系。"
异步消息减少调用方等待,但消费处理时间、确认机制、重投和幂等仍然需要设计。
"只要消息大小没有超限,就适合直接传输。"
大小限制只是一个条件,还要关注吞吐、并发、内存和失败重试成本。
七、结语
面对跨系统的大 JSON,先判断瓶颈来自哪里:是报文大小限制、带宽与解析成本,还是同步等待中的长时间计算。
压缩通过减少报文字节数改善传输;外置数据通过传引用改变交互方式;异步任务通过受理、执行和结果获取分离,减少长时间等待。
把这三个问题分别想清楚,再根据真实测量结果组合方案,才能让系统在数据规模增长后仍然保持可控。