Skip to content

对账中心系统设计 ​

定位:幂等保证单笔正确,对账中心保证存量正确。它是资金系统的巡检雷达,只读分析、只产差异单、永不直接修数。三横一纵的概念与差错处理语义见幂等与对账-资金安全底座。文件批次协议见清结算与资方对接。本篇回答系统层问题:模块怎么划、文件怎么解析、亿级数据怎么对碰、差异怎么自动化收口。

一、模块划分:三横一纵 + 差错中心 ​

三横一纵的概念定义与各层语义见幂等与对账-资金安全底座,此处不重复展开。各层语义:渠道对账挡外部、内部对账挡跨系统、总分核对挡账内、余额连续性防状态篡改。系统层做两件事。一是把四条核对线落成模块(按下表的数据源与节奏划分)。二是在其上加第⑤块差错中心统一收口:

模块数据源节奏抓的错
渠道对账渠道/资方账单文件T+1掉单、掉账、金额不符、手续费口径
内部对账业务事件 vs 账务凭证准实时增量 + T+1 全量事件丢失、记账漏记
总分核对总账/分户账日切汇总批漏跑、明细汇总脱节
余额连续性全账户流水日切任何人直接 UPDATE 过余额
差错中心上述全部产出常驻(收口,不抓错)

模块化纪律:对账中心是独立的系统(独立库、独立算力、独立告警),不复用业务系统代码。它必须能怀疑所有人,包括生产数据的那些系统。

二、对账文件解析插件化 ​

渠道账单格式千人千面(CSV/定长/加密 Excel/自造格式)。解析层用插件隔离差异,核心引擎只认统一中间模型:

java
public interface ReconFileParser {
    boolean supports(ChannelFile file);              // 渠道+版本路由到具体插件
    FileManifest verifyManifest(ChannelFile file);   // 批次清单三要素: 行数/金额合计/MD5
    Stream<CheckRecord> parse(ChannelFile file);     // 流式解析, 整文件不进内存
}

public class CheckRecord {                            // 统一中间模型(核心引擎唯一输入)
    private String  channelTradeNo;   // 渠道交易号(对碰主键, 渠道侧唯一)
    private String  bizOrderNo;       // 我方业务单号
    private BigDecimal amount;
    private String  direction;        // 收/付
    private String  status;           // 终态
    private LocalDate acctDate;       // 账务日期
}
sql
CREATE TABLE recon_batch (             -- 对账批次: 幂等重跑的单位
  id            BIGINT PRIMARY KEY,
  recon_date    DATE NOT NULL,
  channel_code  VARCHAR(32) NOT NULL,
  recon_type    VARCHAR(16) NOT NULL,  -- CHANNEL/INTERNAL/BRANCH
  file_manifest VARCHAR(512),          -- 三要素留档
  status        VARCHAR(16) NOT NULL,  -- DOWNLOADED/PARSED/MERGED/DONE/FAILED
  UNIQUE KEY uk_date_chn_type (recon_date, channel_code, recon_type)
);

插件化的三个工程点:

  • 清单校验先于解析:行数、金额合计、MD5 三要素不过,文件直接拒收。防止"解析到 800 万行发现尾部截断"的算力浪费与半批脏数据。
  • 新渠道接入 = 新增一个 Parser 插件,核心对碰引擎零改动。格式变更 = 插件内加版本分支。协议要素见清结算与资方对接。
  • 解析产物落中间表而非直通对碰:解析与对碰解耦——解析失败重解析,对碰失败不必重新下载解密。

三、对碰算法:排序归并 ​

对碰 = 两个有序流的双指针归并,不是逐条嵌套查询(N×M 是对账系统的第一死法)。双指针逐格走位——两边都有即平、单边即长/短款疑点、单号相同金额不符挂账待查。逐行演示见幂等与对账-资金安全底座的对碰图。系统层关心的是亿级规模的三个实现要点:

  • 排序下推数仓:T+1 全量对碰就是一次 sorted merge join(Hive/Spark 一条 SQL),对账库只接增量与小渠道。应用层外部排序/分片归并只兜小文件。容量问题的答案是"下推给谁",不是堆内存。
  • 对碰主键选渠道交易号:它是渠道侧唯一键。业务单号在掉单场景根本不存在,做主键会把"他有我无"误判成解析失败。
  • 差异率本身是核心监控:昨天 0 今天 0.1%,不需要逐笔看。先怀疑批量脚本或渠道格式变更,先查面再查点。
sql
CREATE TABLE recon_diff (
  id             BIGINT PRIMARY KEY,
  batch_id       BIGINT NOT NULL,
  diff_type      VARCHAR(16) NOT NULL,  -- LONG(长款)/SHORT(短款)/AMOUNT(金额不符)
  biz_order_no   VARCHAR(64),
  channel_trade_no VARCHAR(64),
  our_amount     DECIMAL(16,2),
  their_amount   DECIMAL(16,2),
  status         VARCHAR(16) NOT NULL,  -- 差错单状态机, 见第四节
  root_cause     VARCHAR(32),           -- 闭环后回写根因(掉单/重复/bug/渠道)
  UNIQUE KEY uk_batch_key (batch_id, channel_trade_no, diff_type)
);

四、差错单状态机与自动化处理率 ​

差异单生命周期沿用底座篇的 DISCOVERED(发现)→CONFIRMED(确认归类)→PROCESSING(处理中)→CLOSED(关闭) 状态机。含 FALSE_ALARM(在途到账/重复差异复核后关闭)与 REJECTED(处置方案驳回退回)两条旁路。任一状态停留超 SLA 即升级告警(资金敏感 30 分钟、普通 24 小时)。完整状态机图见幂等与对账-资金安全底座。

自动化处理率的边界——能自动的只有"补信息",改钱永远人工:

差异类型自动化路径自动化程度
长款(掉单)自动调渠道查单 → 复用正常入账幂等路径补单高(90%+)
在途/跨日时差自动挂"在途",T+1 复对自动关闭高
短款(我方误记)只自动建单派人工,冲正凭证必须审批低
金额不符自动比对原始凭证辅助定位,裁决归人工低

自动化处理率 = 自动关闭的差异单 / 全部差异单,是差错中心的核心 KPI。每张关闭的差异单回写根因分类,月度归因反哺幂等与对账规则。自动化率的上限不在脚本写得多好,在上游幂等做得好坏。补单复用幂等路径的逻辑见幂等与对账-资金安全底座。

五、准实时对账的架构取舍 ​

准实时与 T+1 的逐维对照(数据源/延迟/覆盖/成本)见幂等与对账-资金安全底座的对照表。一句话分工:准实时吃交易消息流、分钟级延迟,管掉单快速止血。T+1 吃账单文件与日切快照、次日验收,管资金终态。系统层的实现取舍在下面两处:

准实时的实现是消息驱动的轻量核对:

渠道受理成功(10:00) ──▶ 等待我方业务终态
   ├─ 10:02 终态到达 ──▶ 勾对完成, 结束
   └─ 10:15 仍无终态 ──▶ 准实时对账告警 ──▶ 自动查单
                          ├─ 查到成功 ──▶ 复用幂等入账路径补单
                          └─ 查不到 ──▶ 差错单人工跟进

取舍结论:双轨是必需而非冗余。渠道的资金事实以它的账单文件为准(文件是渠道日切轧账后的终态)。所以准实时买的不是正确性,是发现速度。"渠道成功 15 分钟我方还没入账"在用户投诉前就被发现并触发补单。T+1 是不可绕过的最终验收,准实时规则再全也有盲区(手续费、结算批次只在文件里有)。只有准实时:差异可能永远差一点没人看;只有 T+1:掉单要等用户当客诉报上来。

六、异常与降级 ​

故障预案
对账文件未到超时告警(核心渠道 30 分钟级)+ 催重推通道;差异累积可见,绝不"文件没来当没差异"
对账作业失败批次幂等(uk_date_chn_type)断点重跑,不产生重复差异单
数仓延迟对碰窗口顺延并告警,绝不跳过——跳过的日子就是审计黑洞
准实时链路故障退化为纯 T+1(可接受降级),恢复后补扫在途
差异单积压超阈值SLA 升级 + 差异率告警双通道;联动冻结上游清算(带差异不清算,见清结算与资方对接)

七、追问链 ​

Q1:对账程序为什么永不直接修数据? 两个理由:修数的正确性需要业务上下文(长款可能是掉单也可能是重复推送),自动判断越权。对账系统的信任模型建立在"它只读"上。一旦它能写,它自己的 bug 就成了新的差异源,且无法用对账自证。发现与修复分离,修复复用正常业务路径。

Q2:差异率从 0 突然涨到 0.1%,逐笔查吗? 不。差异率是面指标。ppm 级到 bp 级的跳变几乎必然是系统性原因(批量脚本变更、渠道格式升级、时钟漂移)。先看变更记录与分布(是否集中某渠道/某时段),再抽点验证。"先查面再查点"是差异处理的第一响应纪律。

Q3:内部对账和总分核对是不是重复建设? 不重复:内部对账是业务系统与账务系统之间的跨系统核对(抓事件丢失)。总分核对是账务系统内部总账与分户账的核对(抓汇总脱节)。前者对不上可能业务漏发事件、账务没错,后者对不上一定是账务内部问题。混在一个作业里,每次告警先吵半天归属。

Q4:自动补单会不会把差异修成第二个错? 补单走与正常回调完全相同的幂等入账路径(CAS + 唯一约束),已入账则幂等返回。补单不引入新写路径,幂等性天然继承。真正禁止的是另起炉灶的"修数脚本",那是二次事故之源。

Q5:亿级文件双方流内存放不下怎么办? 排序归并的本质是两个游标,不需要整文件在内存。解析即流式(Stream),排序下推数仓或做外部排序(分片→各片排序→归并),应用层只需 O(1) 的双指针缓冲。对账的容量问题永远是"把排序下推给谁"的问题。

Q6:对账中心自己挂了谁发现? 三层:文件到达监控(渠道文件未到告警独立于对账作业)。作业心跳与批次 SLA(状态超时未推进告警)。外部巡检(日终对账日报缺失即告警)。对账失效是静默失效——它不报警不等于没问题,所以"对账在跑"本身必须被监控。

Q7:差错单为什么要有 SLA 升级,关闭了不就行? 差异单是负债不是记录:开着的差异单意味着有一笔钱归属不明。SLA 升级(资金敏感 30 分钟、普通 24 小时)保证差异单不会变成"永久挂起"。挂账科目账龄巡检(超 N 天升级)与之配套——蓄水池不排水就是事故前兆。

八、面试官视角小结 ​

面试官在对账设计里检查四件事:

  • 模块观:三横一纵是不是按"抓的错"划分的——按渠道或按表划分,说明没想过对账的分治逻辑;
  • 工程底线:对碰是不是排序归并、差异是不是只产工单不修数。嵌套循环对碰和自动改钱是一票否决项;
  • 自动化边界:能不能说清"补信息可自动、改钱必人工"及其原因;
  • 双轨取舍:准实时和 T+1 各买什么,说得出"文件才是资金终态事实源"才算懂。

一句话总结:三横一纵抓四类错,插件解析排序对碰,只产差异永不修数,准实时止血 T+1 验收。

2026-10 新增(金融信贷域 · 系统设计层)。