3. 双写场景解析
在金融账务系统中,“双写”并不是一个简单的技术动作,而是一种跨数据源、跨系统边界的一致性策略。它通常出现在三类典型场景中:数据库与缓存之间、主库与备库之间、核心系统与外围系统之间。这三类场景的写入目标、失败后果和恢复手段差异很大,如果混为一谈,很容易在异常恢复时做出错误决策。
一、核心原理:双写的三种一致性模型
从一致性模型看,双写可以分为三种:强一致双写、最终一致双写和补偿型双写。金融账务中,主库与备库通常追求强一致或准强一致;数据库与缓存通常接受最终一致;核心系统与外围系统之间则大量依赖补偿型双写。
| 场景 | 写入目标 | 一致性要求 | 典型失败后果 | 恢复手段 |
|---|---|---|---|---|
| 数据库与缓存 | DB + Redis | 最终一致 | 缓存脏读、旧值覆盖 | 延迟双删、版本号、对账刷新 |
| 主库与备库 | 主库 + 备库 | 强一致/准强一致 | 主备切换丢数据 | 半同步复制、GTID、日志补偿 |
| 核心与外围 | 核心账务 + 外围系统 | 最终一致 | 外围状态与核心不一致 | 消息重试、冲正、日终对账 |
理解这张表的关键在于:一致性要求越高,双写的同步成本越大,异常恢复的窗口越小。例如主库与备库如果采用异步复制,主库宕机时可能丢失最后一段事务日志,这时备库虽然“写成功”了,但数据并不完整。
二、方法流程:双写场景的通用处理链路
无论哪类双写,都可以抽象为一条通用链路:业务校验 → 主写入 → 副写入 → 结果确认 → 异常补偿。区别在于每一步的失败处理策略不同。
- 业务校验:检查账户状态、余额、幂等键,避免重复写入。
- 主写入:写入权威数据源,通常是核心账务库或主库。
- 副写入:写入缓存、备库或外围系统,可能同步或异步。
- 结果确认:确认副写入是否成功,记录写入日志和版本号。
- 异常补偿:失败时进入重试、冲正、对账或人工干预流程。
以数据库与缓存双写为例,常见做法是“先写数据库,再删除缓存”,而不是“先写数据库,再更新缓存”。因为更新缓存可能被并发读请求覆盖,而删除缓存配合下一次读时回填,更容易保证最终一致。
// 推荐:先写DB,再删除缓存
updateAccount(accountId, amount);
redis.del("account:" + accountId);
// 不推荐:先写DB,再更新缓存
updateAccount(accountId, amount);
redis.set("account:" + accountId, newValue); // 可能被旧读请求覆盖
三、案例/场景:三类双写的真实差异
场景一:数据库与缓存双写。某支付系统在扣款成功后更新账户余额,同时删除缓存。如果删除缓存失败,下一次读请求会从数据库加载旧值并回填,导致用户看到错误余额。解决方式是引入版本号:缓存中存储数据版本,数据库更新时版本递增,回填时比较版本,旧版本不覆盖新版本。
场景二:主库与备库双写。核心账务库采用一主两备架构,主库写入后通过半同步复制将日志发送到备库。如果备库确认超时,主库会降级为异步复制,此时主库宕机可能丢失最后若干事务。恢复时需要通过GTID定位差异,从主库二进制日志中提取缺失事务,在备库重放。
场景三:核心系统与外围系统双写。核心账务系统完成记账后,需要通知外围的积分系统、风控系统和通知系统。这类双写通常通过消息队列异步完成。如果消息发送失败,核心系统不能回滚已记账事务,而是将消息写入本地消息表,由定时任务重试,直到外围系统确认消费。
-- 本地消息表示例
CREATE TABLE outbox_message (
id BIGINT PRIMARY KEY,
biz_id VARCHAR(64),
payload TEXT,
status VARCHAR(16), -- PENDING / SENT / CONFIRMED
retry_count INT DEFAULT 0,
created_at DATETIME,
updated_at DATETIME
);
四、误区与复盘:双写中最容易犯的四个错误
- 误区二:缓存更新与数据库更新顺序随意。先更新缓存再写数据库,一旦数据库写入失败,缓存就是脏数据;先写数据库再更新缓存,可能被并发读覆盖。正确做法是写数据库后删除缓存,并配合版本号或延迟双删。
- 误区三:主备双写当成普通双写。主备之间是复制关系,不是业务双写。业务只写主库,备库由复制机制同步。如果业务代码同时写主库和备库,会破坏复制链路,导致数据错乱。
- 误区四:核心与外围双写失败后直接回滚核心事务。核心账务已经记账,外围通知失败不应回滚核心,而应通过本地消息表、重试和对账来补偿。回滚核心会引发更大的账务问题。
复盘时,建议围绕三个问题展开:写入顺序是否合理?失败后是否有补偿路径?对账能否发现不一致?如果这三个问题都有明确答案,双写方案才算具备生产可用性。否则,双写只是把风险从写入时刻推迟到了对账时刻。