内存化在对账系统中的应用实践 📅 2026/7/8 1:19:32 一、重新认识对账问题很多系统里对账就是两张表、一条 JOIN、一个定时任务。流水量小的时候没问题。但量上来之后JOIN 越来越慢跨周期的逻辑越来越绕锁、事务、补偿代码层出不穷。根子在哪对账不是 CRUD是一个持续推进的状态机。它有几个特点流水量大但生命周期有限同一条流水只能处理一次顺序和确定性很关键。这几点加在一起恰好是内存化最合适的场景。二、传统方案为什么越做越重1. MySQL适合存档不适合推进MySQL 做结果存档没问题但对账需要的是过程计算性能被 IO 和索引卡住复杂事务引发锁竞争中间状态被迫拆成多张表对账进度从表结构里根本看不出来流水到千万级对账系统就变成了一堆 SQL 在和时间赛跑。2. Flink能用但资源花在了别处先说结论Flink 做对账不是不行是有点重。Flink 的优势在于来源多、乱序严重、迟到数据多的场景。用keyBy state watermark处理乱序确实优雅。但对账的真正瓶颈往往不是能不能算出来而是吞吐够不够、成本能不能接受、复杂度能不能长期维护。落地下来Flink 的问题集中在三块State 落在 RocksDB 上未匹配流水多的时候磁盘 IO 很高Checkpoint 在状态大、变化频繁的对账场景下容易成为瓶颈State 膨胀、反压这些运维问题比较常见所以 Flink 做对账是能用但单位资源能跑多少流水比内存化差不少。三、内存化对账的核心思想先说一个常见误解内存化不是为了快把数据库换成内存。它改变的是状态管理方式。内存就是状态机。对账状态只在内存里内存里有什么系统当前就是什么状态。没有内存一份、数据库又一份的数据冗余。单写入者顺序执行。同一个对账任务只有一个线程在写所有变更按顺序发生。不需要锁不需要事务也不需要对并发写同一条流水做任何处理。可重建而不是不能丢。内存数据可以丢但丢了之后要能重新建出来。这是后面对账文件当 WAL 用能成立的前提。四、对账文件就是 WAL对账场景有个常被忽略的事实对账文件本身就是天然的 WAL。文件记录的是已经发生的事顺序固定、可重放不需要再设计额外的日志系统。快照不是全量流水的备份而是一个或多个对账周期结束后内存里还没对平的那部分流水。恢复流程加载最近快照然后从对账文件里继续往后推进。就这两步。五、整体流程对账流程分几个阶段数据获取。来源可以是对账文件、数据库导出、FTP 或 HTTP 上传具体形式不重要。核心是拿到某个对账周期1 分钟、1 小时、1 天内的数据截断。预处理可选。多文件合并、按业务时间排序、处理上游乱序。上游已经有序的话这步直接跳过。读取。顺序读每个业务方的对账文件生成流水唯一 ID写入各自的内存队列。把外部数据稳定地变成内存事件。对账。从一个队列里取数据去另一个队列里找匹配。对平了就从两个队列里都删掉没对平就放回队列尾部等后续流水。持久化。每个周期结束后把内存里还没对平的数据存成快照单边账写入数据库配置和周期相关数据用单独的表维护。运行流程六、内存队列模型队列怎么推进读取线程往队列尾部写流水对账线程从头部取流水去匹配。能对平的直接删不能对平的放回尾部。循环往复队列里的数据慢慢被消化掉。内存里剩下的就是当前系统状态也是快照的内容。重启后从最新快照恢复内存再从快照点之后的对账文件重新开始对账。跨周期不是异常不同系统生成的对账文件同一条业务流水的时间戳可能不同有时候会落到不同的对账周期里。所以一个周期内没匹配上不代表是异常。当前周期的未对平流水会留下来下个周期继续尝试。连续多个周期都没对平才判定为异常。这样可以避免因为周期边界问题产生大量误报。有序是前提队列模型能跑起来两边的流水得大致有序。严重乱序的话大量数据长期无法匹配队列消不掉内存就会一直涨最终把对账任务撑死。实际工程里要么在预处理阶段排序要么对上游提顺序约束。周期必须同步对账任务和所有来源任务必须在同一个周期里。对账还在处理 9 月 15 日某个来源任务已经在读 9 月 16 日的数据状态就乱了。七、吞吐量对比对账系统通常不太在意单条延迟更关心同样机器资源下一分钟能对多少流水。方案单机吞吐量量级主要限制因素MySQL万级 / 分钟IO、索引、锁Flink十万级 / 分钟State、Checkpoint内存化百万级 / 分钟内存容量与带宽Flink 的绝对吞吐量不差但资源花在了通用性和分布式能力上。内存化方案只解决对账这一件事相同资源下算力更集中吞吐通常高一个数量级延迟分布也更稳定。八、代价与边界说说缺点。状态全在内存里风险也集中。设计阶段要克制分区方式直接决定系统上限。但对账场景顺序明确、状态可回收这两点恰好是内存化最能发挥的地方。