缓存与数据库一致性的核心方案
一、旁路缓存 (Cache Aside)
适用场景:读多写少的业务场景,是目前业务开发中最常用的缓存一致性方案。
1. 核心执行逻辑
读流程
优先查询缓存,若缓存未命中,再查询数据库,最后将数据库查询结果回写至缓存,供后续请求复用。
写流程
先更新数据库,再删除缓存数据。
核心说明 :写操作优先选择删除缓存而非更新缓存。删除操作具备简单、幂等的特性,能够有效规避并发写场景下的数据一致性问题,避免缓存数据与数据库数据错乱。
2. 核心痛点及解决方案
痛点一:高并发读写一致性问题
受网络延迟、读写并发、数据库主从同步延迟、事务未提交等因素影响,会出现缓存数据被污染的问题,具体执行流程如下:
-
线程A执行数据更新操作,率先完成数据库数据更新;
-
线程A随即删除对应缓存数据;
-
此时线程B发起数据读取请求,缓存为空触发数据库查询逻辑;
-
因数据库主从同步延迟、线程A事务未完全提交等原因,线程B查询到数据库旧数据;
-
线程B将查询到的旧数据回写入缓存,最终形成数据库为新数据、缓存为旧数据的不一致状态,且该错误数据会一直留存,直至缓存过期。
常规优化方案 :延迟双删策略,延迟时间常规设置为 200ms~500ms,错开读写并发的时间窗口,避免旧数据回写缓存。
痛点二:缓存删除失败问题
若直接删除缓存失败,会导致缓存留存旧数据,引发数据不一致。可将缓存删除操作交由MQ处理,依托MQ的消息重试、持久化机制,保障缓存删除操作最终执行成功。
3. 终极解决方案:Canal 监听 Binlog
通过Canal组件监听MySQL的Binlog日志(读写分离架构下监听从库Binlog),异步感知数据库数据变更,自动执行缓存删除或更新操作。该方案对业务代码完全无侵入,可靠性极高,可同时解决上述两大痛点。
方案核心原理(MySQL 两阶段提交机制)
-
Prepare阶段:InnoDB引擎将数据写入redo log日志,并将事务状态标记为prepare;
-
Binlog写入阶段:记录数据逻辑变更信息,写入Binlog日志并刷盘持久化;
-
Commit阶段:InnoDB将redo log事务状态更新为commit,完成事务提交。
MySQL宕机恢复机制:若事务在Commit阶段宕机,重启后MySQL会校验Binlog完整性。只要Binlog日志已完整写入,即便redo log处于prepare状态,MySQL也会判定事务有效并自动提交。因此,只要Canal读取到Binlog日志,即可判定数据库数据已提交生效,以此保障缓存更新的准确性。
方案优势
传统MQ手动更新方案需改造业务代码,针对Redis、ES、大数据平台等不同中间件单独发送消息,代码冗余且扩展性差。而Canal可作为统一数据分发中心,通过下游配置多组消费者,分别处理Redis缓存更新、ES索引更新、HBase数据同步等需求,大幅简化业务架构。
4. 实战落地
实际开发中,可基于RedisTemplate、Jedis等主流Redis客户端,二次封装通用的CacheService缓存工具类,统一处理缓存查询、删除、更新等操作。
二、读/写直通 (Read/Write Through)
核心特性 :保障缓存与数据库强一致性,适用于对数据准确性、一致性要求极高的核心业务场景。
1. Read Through(读直通)
由缓存服务统一接管数据查询逻辑。业务请求查询数据时,若缓存未命中,缓存服务主动同步查询数据库,将数据加载至缓存后,再返回结果给业务应用,应用无需直接操作数据库。
2. Write Through(写直通)
由缓存服务统一接管数据写入逻辑。业务应用仅需写入缓存,缓存服务会同步将数据更新至数据库,仅当数据库写入成功后,才向应用返回写入成功结果。
3. 核心痛点
所有读写操作均需同步联动数据库,链路耗时较长,接口响应延迟高,并发性能较差。
4. 技术实现
Ehcache缓存框架原生提供CacheLoader(读加载)、CacheWriter(写写入)接口,开发者完成配置后,调用cache.put()等缓存方法,框架会自动执行数据库CRUD操作,无需手动编写SQL。
三、异步写回 (Write Back / Write Behind)
核心特性:以数据一致性为代价,极致提升写入并发性能,适用于高并发、弱一致性业务场景。
1. 执行逻辑
业务应用写入数据时,仅更新缓存即可立即返回成功结果,无需等待数据库写入。缓存服务会异步、批量将缓存中的增量数据刷入数据库,大幅提升接口吞吐能力。
2. 解决问题
完美适配超高并发写入场景,避免大量高频写请求直接冲击MySQL,导致数据库压力过载、宕机。
3. 核心痛点
存在数据丢失风险。若缓存节点在数据异步落库前宕机,未同步至数据库的缓存更新数据会永久丢失,无法恢复。
4. 典型应用场景
-
社交媒体统计数据:点赞数、阅读量、浏览量等场景,少量数据误差不影响用户体验,但并发量极高,无法直接落地数据库;
-
操作系统磁盘缓存:系统write()文件写入调用,采用的就是Write Back机制,数据先写入内存Page Cache,再由内核线程异步刷入硬盘。