双写策略是什么
双写策略(Dual Write Strategy)是指在分布式系统中,为了保持多个数据存储(如数据库和缓存,或主从数据库)之间的数据一致性,采用同时写入两个存储的同步操作方式。即在更新数据时,先或同时向两个目标(如 MySQL 和 Redis)写入数据,确保两边数据一致。
1. 双写策略详解
定义
- 双写策略通常用于解决缓存与数据库一致性问题,或多数据源同步场景。
- 核心思想:每次写操作都同时更新所有相关存储,避免异步更新带来的延迟或不一致。
实现方式
- 先写数据库,后写缓存:
- 更新数据库 -> 更新缓存。
- 先写缓存,后写数据库:
- 更新缓存 -> 更新数据库。
- 同时写入:
- 并行写入数据库和缓存。
示例(Java + MySQL + Redis)
public void updateUser(String userId, String name) {
// 写数据库
userDao.update(userId, name);
// 写缓存
redis.set("user:" + userId, name);
}
2. 双写策略的场景
- 缓存一致性:
- 如 Redis 缓存与 MySQL 数据库同步。
- 主从同步:
- 主库写完立即同步从库。
- 异构数据源:
- 如同时更新 ES(ElasticSearch)和数据库。
3. 优点与缺点
优点
- 一致性强:
- 数据更新实时同步,减少不一致窗口。
- 简单性:
- 逻辑直观,易实现。
- 低延迟:
- 无需异步任务,立即生效。
缺点
- 性能开销:
- 同步写多个存储,增加响应时间。
- 失败风险:
- 一个存储失败,需回滚或补偿,复杂化事务。
- 耦合性:
- 业务代码需处理多存储逻辑。
示例问题
- 更新 MySQL 成功,Redis 失败:
- 数据不一致,需回滚 MySQL 或重试 Redis。
4. 实现中的优化与解决
(1) 事务保障
- 用分布式事务(如 XA 或 TCC)保证原子性。
- 问题:性能低,复杂度高。
(2) 失败重试
- 一个存储失败时,重试写入。
public void updateUser(String userId, String name) {
userDao.update(userId, name);
int retries = 3;
while (retries-- > 0) {
try {
redis.set("user:" + userId, name);
break;
} catch (Exception e) {
Thread.sleep(100); // 重试间隔
}
}
}
(3) 异步补偿
- 双写失败后,记录日志,异步任务修复。
- 示例:用 MQ 发送补偿消息。
(4) 先删缓存
- 先删除缓存,写数据库后异步更新缓存,降低不一致性。
redis.del("user:" + userId);
userDao.update(userId, name);
5. 与其他策略对比
- 双写策略:
- 同步写,强一致,低延迟。
- 异步写:
- 如 Canal 监听 binlog 更新缓存,高吞吐但有延迟。
- 读修复:
- 读时检查一致性再修复,适合读多写少。
延伸与面试角度
- 一致性模型:
- 双写追求强一致性,异步追求最终一致性。
- 实际应用:
- 电商:库存同步到 Redis 和 MySQL。
- 日志:写 ES 和数据库。
- 优化点:
- 分布式锁:防止并发双写冲突。
- 批量写:减少 IO。
- 面试点:
- 问“双写问题”时,提失败回滚。
- 问“替代”时,提 Canal 或读修复。
总结
双写策略通过同步写入多个存储保证数据一致,简单但有性能和失败风险。优化靠事务、重试或异步补偿。面试时,可结合 Redis 示例或提一致性对比,展示理解深度。