1. 整体描述
一个报表生成任务,可能既要计算核心指标,也要调用第三方接口补充对象名称、分类标签等描述信息。
如果把这些步骤放在同一条必须全部成功的执行链路中,第三方接口超时、认证失败或返回异常,就可能让已经能够完成的核心计算一起失败。
本文结合一套报表补齐任务,介绍如何把核心生成与第三方依赖分开,让外部服务暂时不可用时,核心结果仍可查询,辅助信息可以随后补齐。
客户名称、系统名称、接口、字段、表名、对象标识和业务规则均已泛化。文中的任务名称、状态模型、代码及日志示例用于解释设计,不对应真实客户数据,也不代表新增了实际接口或配置。
处理顺序如下:
text
核心生成任务
↓
读取业务数据、计算明细和汇总
↓
提交核心报表,辅助字段允许暂缺
↓
报表可以查询和导出
独立补齐任务(在核心生成完成后调度)
↓
确定报告周期、查询待补齐对象、获取认证信息
↓
逐个查询第三方、匹配对象、转换字段
↓
将可处理结果更新到对应周期的报表
↓
失败对象保留原值,后续再次尝试
需要区分两个结果:
- 核心任务完成:业务明细和指标计算成功,报表具备约定的可用性。
- 辅助信息完整:第三方提供的补充字段已经按规则处理并保存。
这套方式成立的前提是,第三方字段允许暂缺。如果外部数据直接决定核心计算的正确性或业务是否允许继续执行,就不能把调用失败简单降级为空值。
2. 具体描述
2.1 先判断外部数据是否属于核心依赖
拆任务之前,先检查第三方字段的实际用途。
| 外部数据用途 | 需要判断的问题 | 可采用的处理方向 |
|---|---|---|
| 描述名称、辅助标签 | 暂缺是否影响核心结果解释 | 允许暂缺,独立补齐 |
| 核心计算输入 | 缺失后计算结果是否仍然正确 | 明确缺失处理,必要时停止计算 |
| 执行许可条件 | 未取得结果是否允许业务继续 | 按业务约束决定,不默认放行 |
| 查询装饰信息 | 是否必须每次查询都实时获取 | 考虑落地结果或缓存及其时效 |
本文场景中,第三方信息用于补充已经生成的报表记录,不参与核心数值计算。核心结果先生成、辅助信息后补齐,符合这一业务前提。
如果页面把缺失字段解释为某个确定分类,即使后端允许为空,也可能产生误导。字段暂缺、明确否定和查询失败的语义,需要在查询与导出中保持一致。
2.2 用独立任务建立失败边界
参考实现将核心报表生成和第三方补齐放在两个调度入口中。
核心任务负责生成并保存明细与汇总;补齐任务读取已有报表,只更新约定的辅助字段。
text
核心生成提交成功
↓
补齐执行失败
↓
保留核心结果和原有辅助值
这比在核心事务中调用远程接口更容易限定失败影响。补齐失败不会要求回滚此前已经独立提交的核心生成结果。
这里的"独立异步"指相对于核心任务单独调度,补齐任务内部仍然可以同步、顺序调用第三方。它不意味着已经使用消息队列、并行调用或异步 HTTP 客户端。
两个任务的先后关系也需要由调度编排确认。仅配置不同执行时间,不能保证核心任务遇到延迟时,补齐仍一定在其后运行。
即使两个入口已经分开,它们也可能共享线程池、数据库连接和机器资源。逻辑解耦减少了直接失败传播,但资源耗尽仍可能影响核心任务。
2.3 调度、业务编排和远程调用分别负责什么
参考实现可以分为三部分:
| 部分 | 主要职责 |
|---|---|
| 任务入口 | 记录任务开始与结束,调用业务服务,处理任务级异常 |
| 补齐服务 | 确定处理范围、去重对象、解析结果、转换字段和保存更新 |
| 第三方客户端 | 构造请求、携带认证信息、反序列化响应和处理调用异常 |
任务入口保持轻量,有助于把周期计算和数据处理放在可以单独验证的位置。
客户端处理 HTTP 异常,业务服务处理成功响应中的业务状态和对象匹配。HTTP 请求成功,不代表响应内容一定满足补齐条件。
当前客户端把多种调用异常记录后统一返回空结果,业务服务将其按失败处理。这能避免常见远程异常直接中断对象循环,但也损失了失败分类信息。
如果后续需要区分认证失败、限流、超时和响应格式错误,应使用明确的调用结果类型,而不是只返回 null。这是可选增强,并非当前代码已经具备的机制。
2.4 按报告周期和对象标识限定更新范围
同一个对象可能出现在多个报告周期,也可能在同一周期中对应多条汇总记录。
补齐应先确定周期,再查询该周期内去重后的对象集合。每个对象只查询一次,结果用于更新这一周期中对应的记录。
泛化后的更新范围可以表示为:
sql
UPDATE report_summary
SET display_name = :displayName,
category_label = :categoryLabel,
category_raw = :categoryRaw
WHERE report_period = :reportPeriod
AND object_code = :objectCode;
所有参数均通过数据库访问框架绑定。更新条件不能只保留对象标识,否则可能误改其他周期的历史结果。
参考任务根据当前日期推导上一报告周期,不是一个支持任意周期重放的通用入口。跨周期补偿时,不能假定再次执行同一个任务就一定处理原来的范围。
长期需要补偿历史任务时,可以增加明确的周期参数和范围校验,并将任务范围保存下来。这属于后续设计。
2.5 获取认证失败时,结束补齐而不破坏报表
参考实现先获取一次访问令牌,再对对象集合逐个查询。
令牌获取异常或结果为空时,补齐记录错误并结束,不向数据库提交清空字段的更新。此前核心任务已经保存的结果继续保留。
这里需要注意运行时长。一次认证后顺序处理全部对象,可能在中途遇到令牌过期。是否刷新、何时重试,以及重试是否纳入任务预算,需要结合认证协议设计。
当前实现没有据此形成自动刷新闭环,不能将"任务开始时获取令牌"描述为"整个任务始终具有有效认证"。
2.6 将调用失败与成功但无数据分别处理
补齐最容易出错的地方,是把所有没有拿到字段的情况都转换为数据库空值。
本例采用以下处理规则:
| 结果 | 处理方式 | 对原有值的影响 |
|---|---|---|
| 网络异常、认证失败、业务状态失败 | 本次不提交该对象的更新 | 保留原值 |
| 成功响应,但没有匹配对象 | 按本例契约生成未命中结果 | 允许设置为空 |
| 有匹配对象,但无法选择有效版本 | 按本例契约作为无法确定有效记录处理 | 允许设置为空 |
| 有有效记录,但某字段缺失 | 对字段分别按规则处理 | 只按约定修改对应字段 |
| 有有效记录和有效字段 | 转换后生成更新结果 | 保存新值 |
调用失败说明本次没有可靠取得结果,不能据此认定第三方明确返回了空数据。
成功但未命中是否清空旧值,则属于业务契约。本文案例采用清空策略;其他系统也可能要求保留旧值并标记待核对,不能直接照搬。
同样,某个描述字段缺失,不应该妨碍其他有效字段正常处理。用于分类的默认值,也必须由明确规则定义,不能用"方便展示"代替业务语义。
2.7 多条返回记录需要先匹配对象,再选择版本
即使请求只查询一个对象,客户端也应核对响应中的对象标识,避免将其他对象的信息写入当前记录。
当同一对象有多条返回记录时,需要按契约选择版本。参考实现解析来源中的周期编码,比较年份与周期,无法解析的候选不参与比较;相同周期保留返回列表中靠前的记录。
这种选择方式有两个边界:
- 周期编码解析必须符合来源格式,不能把宽松解析视为完整格式校验。
- 如果第三方不保证列表顺序稳定,相同周期取第一条可能在不同调用中得到不同结果。
更重要的是,"当前返回记录中最新的一条"不一定是"报告周期当时有效的一条"。如果报表要求历史时点一致性,就需要按业务日期选择版本,或保留当时使用的来源快照。
2.8 顺序查询可以部分成功,但持久化仍有独立边界
当前实现的处理方式是:逐个对象查询,跳过调用失败的对象,把成功解析的更新结果先收集在内存,循环结束后统一写库。
text
查询对象 A → 收集更新结果
查询对象 B → 调用失败,跳过
查询对象 C → 收集更新结果
↓
统一更新 A、C
因此,远程查询可以出现部分成功,但"已经收集更新结果"不代表数据库已经保存成功。
| 失败位置 | 可能的结果 |
|---|---|
| 某次远程调用失败并被客户端处理 | 后续对象继续执行 |
| 循环中出现未处理异常或进程退出 | 已收集的更新可能尚未保存 |
| 最终批量更新失败 | 需要核对数据库实际结果,不能按查询成功数报告落库成功 |
当对象规模、单次 SQL 大小或任务时长增长时,可以考虑分批持久化,减少一次失败需要重新处理的范围。但这也需要定义批次事务、进度和失败对象记录。
本文不把当前实现描述为"每个对象更新失败都与其他对象完全隔离"。现有更新是集中执行的,数据库失败仍可能影响这一批结果。
2.9 重复执行不等于历史结果保持不变
补齐通过更新既有报表记录实现,不需要为每次调用新增一条汇总记录。
对于相同周期、相同对象和相同来源结果,重复更新可以保持业务字段值一致。但第三方数据变化后,再次执行仍可能写入不同的辅助信息,更新时间等审计字段也可能变化。
参考实现会查询目标周期内的对象集合,并没有只选择失败对象的持久化状态过滤。因此,重复执行既可能补齐失败记录,也可能重新查询并刷新此前成功的记录。
需要分别定义:
- 重试范围:只处理失败对象,还是重新处理全部对象。
- 覆盖规则:成功字段是否允许被新结果覆盖。
- 历史规则:同一周期的报表是否允许因补齐重跑而变化。
当前报表查询与导出读取已落地字段,不在用户查询时实时调用第三方。这减少了查询链路依赖,但不等于补齐任务无法改变历史结果。
如果要求严格复现,应保存来源版本、补齐时间或结果快照,并明确冻结及人工修订规则。
2.10 超时和重试需要有明确预算
核心任务与补齐任务分开之后,补齐仍可能被慢调用长时间占住。
当前客户端没有显式设置连接和读取超时,也没有在这条链路中配置专门的重试、限流或熔断策略。因此,不能声称它已经具备有界调用或自动恢复能力。
继续完善时,可以围绕以下预算设计:
| 预算 | 需要明确的内容 |
|---|---|
| 连接与读取等待 | 各自允许等待多久,使用的客户端是否支持并实际生效 |
| 单对象重试 | 哪些错误可重试,最大次数和累计时长 |
| 整体任务时长 | 何时停止新增调用,剩余对象如何记录 |
| 调用速率与并发 | 第三方承载限制与本地资源上限 |
重试适用于可能恢复的暂时性失败;错误凭据、参数不合法和稳定的格式不兼容,需要先修复原因。第三方限流时,还应遵循其返回的重试约定。
顺序调用有助于控制同时在途请求数,但不等于已经实现请求速率限制。也不能为了缩短任务时间,直接为全部对象创建无界并发。
2.11 用可观察的状态表达部分完成
参考任务会记录对象总数、查询失败数、待更新结果数、空字段数量和数据库更新结果。
这些计数的统计对象不同:一个对象可以对应多条报表记录,空字段对象也可能包含在待更新集合中。不能把所有日志字段直接相加。
下面是一组虚构的对象级统计,假设最终持久化已经确认成功:
text
jobId=ENRICH_DEMO
period=PERIOD_DEMO
objects=160
persistedWithData=143
persistedNoMatch=9
requestFailed=8
这里按互斥对象状态分类,满足 160 = 143 + 9 + 8。实际受影响的数据库行数可以不同于对象数。
生产监控至少需要区分核心生成状态和辅助补齐状态。更细的对象状态可以包括待处理、成功、未命中、调用失败和写入待核对,但当前实现没有完整持久化这套状态模型。
此外,认证失败被记录后直接返回时,调度入口未必会收到异常。调度显示执行完成,不代表对象全部补齐,不能只依赖调度成功标记判断业务完整性。
日志应记录任务范围、匿名对象标识、失败分类和耗时,避免输出令牌、完整报文或包含真实业务信息的响应体。示例日志不使用真实运行记录。
2.12 验证失败隔离,应检查已有结果是否保留
这类任务的验证重点,是失败发生后核心结果、辅助字段和后续对象分别处于什么状态。
| 场景 | 应检查的性质 |
|---|---|
| 第三方整体不可用 | 已提交核心报表仍能按约定查询和导出 |
| 认证获取失败 | 不清空原有字段,不影响核心生成结果 |
| 单对象查询失败 | 保留该对象原值,后续对象继续处理 |
| 成功但无匹配记录 | 按明确契约处理,不混同于调用失败 |
| 响应包含其他对象 | 不发生跨对象更新 |
| 多版本及并列版本 | 选择规则与来源契约一致 |
| 部分字段缺失 | 有效字段与缺失字段分别处理 |
| 最终更新失败 | 不把已查询数量误报为已保存数量 |
| 重复执行 | 更新范围正确,覆盖行为符合历史策略 |
| 核心重建与补齐重叠 | 不把旧任务结果误写到新一代报表中 |
最后一种情况需要特别设计:只有"周期 + 对象"条件,不能区分同一周期的两次重建。如果允许并行重建,可以考虑增加报表生成批次条件;若不允许,则需要调度协调。
这些是验证观察点,不表示已经执行全部测试。本文不提供未经测量的成功率、时延或可用性保证。
3. 总结
当第三方字段允许暂缺时,可以通过独立补齐任务,让核心计算与外部调用分别完成。
第一,先明确业务前提。只有不影响核心正确性的辅助信息,才适合采用先生成、后补齐的方式。
第二,建立独立提交与调度边界。核心结果先保存,第三方失败只影响补齐阶段;同时确认任务顺序和共享资源的影响。
第三,区分调用失败、成功未命中和字段缺失。失败保留原值,清空或默认值依据明确契约执行。
第四,按报告周期和对象限定更新,分别记录查询、匹配和保存结果。重复执行与历史不可变需要不同机制支持。
第五,继续完善超时预算、失败记录和补偿范围。独立任务提供失败隔离的基础,有界调用、可靠恢复与完整状态仍需单独落实。
附:实现检查表
| 检查项 | 需要确认的内容 |
|---|---|
| 业务前提 | 第三方字段暂缺不影响核心计算正确性 |
| 任务边界 | 核心生成与补齐独立提交,失败影响明确 |
| 执行顺序 | 补齐基于已完成的核心结果运行 |
| 更新范围 | 周期、对象及必要的生成批次条件完整 |
| 认证失败 | 不清空字段,状态可观察 |
| 结果分类 | 失败、未命中、字段缺失分别处理 |
| 版本选择 | 对象匹配、版本解析及并列规则明确 |
| 持久化 | 区分内存更新集合与已保存结果 |
| 超时与资源 | 底层等待、重试和整体预算有界 |
| 重复执行 | 重试范围、覆盖规则和历史策略明确 |
| 完成状态 | 核心完成、补齐完成和部分失败分别表达 |
| 日志 | 保留定位信息,避免令牌与真实响应数据泄露 |
标签: Java、第三方接口、异步任务、失败隔离、数据补齐、补偿处理