缓存与DB在秒杀业务中的一致性保障问题

1.缓存与DB在秒杀业务中的一致性保障业务设计架构图:

这里写图片描述

2.资源锁定与回滚策略

业务域划分:
库存概念:下单减库存、支付减库存
交易概念:下单(未支付订单)、已支付、退款未发货

资源行为划分:
资源锁定:下单/支付
资源回滚:订单取消、订单(事务)超时、退款未发货

由于库存与交易业务处于分布式事务中,数据一致性难以保证,我们通过TCC加 MQ 柔性事务回滚机制保证库存数据的一致性。

此类场景可以延伸到优惠券、虚拟币、代偿支付等领域中。

<think>好的,我现在需要回答用户关于使用Cache Aside模式解决数据库缓存一致性问题的请求,并且要提出相关的面试问题。首先,我需要回顾Cache Aside的基本概念和工作流程,确保自己对这一模式的理解正确。 Cache Aside模式,也叫旁路缓存策略,是处理数据库和缓存一致性的常用方法。它的核心思想是在读取数据时先查缓存,命中则返回,未命中则查数据库并更新缓存。写入数据时,先更新数据库,再使缓存失效。这样可以减少缓存数据库不一致的情况。 接下来,我需要详细分解这个模式的各个步骤,确保用户能够清晰理解。比如,读流程和写流程的具体操作,以及为什么要这样设计。例如,在写操作中,为什么是先更新数据库再删除缓存,而不是更新缓存?这是因为在高并发场景下,如果先更新缓存,可能导致其他线程读取到旧数据,特别是在数据库更新失败的情况下,缓存中的新数据就会数据库不一致。 然后,我需要考虑用户可能关心的异常情况,比如并发读写时的问题。例如,当两个线程同时更新同一个数据时,可能会出现缓存不一致的情况。这时候需要分析具体的时序,比如经典的“先写数据库再删除缓存”过程中,如果删除缓存失败,该怎么处理。可能需要引入重试机制或者消息队列来确保缓存最终被删除。 另外,用户可能想知道如何在实际项目中实现Cache Aside模式,是否需要配合其他机制,比如延迟双删或者设置缓存过期时间。这些细节都需要在回答中提及,以展示全面的理解。 接下来是面试问题的准备。我需要列举常见的面试问题,覆盖基本原理、异常处理、优缺点、其他模式的对比等。例如,为什么选择先更新数据库再删除缓存?如何处理缓存删除失败的情况?Cache Aside和Read/Write Through模式有什么区别?这些问题能够帮助面试官评估候选人对缓存一致性问题的理解深度。 同时,还要考虑实际应用中的问题,比如在分布式系统中如何保证原子性操作,或者在高并发场景下如何优化性能。例如,使用异步队列来处理缓存删除失败的情况,或者使用分布式锁来避免并发写入导致的数据混乱。 最后,我需要验证自己的思路是否正确,是否有遗漏的关键点。例如,Cache Aside模式是否适用于所有场景?比如对于写多读少的场景,频繁删除缓存可能导致缓存命中率下降,这时候可能需要其他策略,如Write Behind等。这也是面试中可能被问到的问题,需要在回答中提及。 总结来说,我需要构建一个结构清晰、覆盖全面的回答,分步骤解释Cache Aside模式,并列举相关的面试问题,确保用户不仅理解基本概念,还能应对实际面试中的深入提问。</think>以下是关于Cache Aside模式的详细解析及常见面试问题: --- ### 一、Cache Aside模式核心原理 #### 1. 基本流程(两步操作) **读操作流程**: ```mermaid graph TD A[客户端请求数据] --> B{缓存是否存在?} B -- 存在 --> C[直接返回缓存数据] B -- 不存在 --> D[查询数据库] D --> E[将数据写入缓存] E --> F[返回数据] ``` **写操作流程**: ```mermaid graph TD A[更新数据库] --> B[删除缓存] B --> C{是否需要重试?} C -- 删除失败 --> D[异步队列重试删除] C -- 成功 --> E[结束] ``` #### 2. 关键设计原则 - **写策略**:先更新数据库,再删除缓存(*非更新缓存*) $$操作序列 = Write\ DB \rightarrow Delete\ Cache$$ - **读策略**:缓存未命中时,从数据库加载后回填缓存 $$操作序列 = Read\ Cache \xrightarrow{Miss} Read\ DB \rightarrow Set\ Cache$$ --- ### 二、解决一致性问题的核心机制 #### 1. 应对并发写入问题 - **场景**:两个并发写操作可能导致缓存数据库不一致 **解决**:通过时序控制确保最终一致性 ```text 时序示例: 1. 线程A更新数据库为X=1 2. 线程B更新数据库为X=2 3. 线程B删除缓存 4. 线程A删除缓存(覆盖了B的删除) → 最终缓存会被后续读请求正确回填 ``` #### 2. 缓存删除失败处理 - **重试机制**:通过消息队列(如Kafka)异步重试删除 ```java // 伪代码示例 try { cache.delete(key); } catch (Exception e) { mq.send(new DeleteCacheMessage(key)); // 发送到重试队列 } ``` - **过期时间兜底**:设置缓存过期时间(如30分钟),防止长期不一致 --- ### 三、典型面试问题及参考答案 #### 1. 为什么选择先更新数据库再删除缓存? - **参考答案**: 1. 若先删除缓存,在数据库更新完成前可能有请求穿透到数据库,导致旧数据回填缓存 2. 先更新数据库能保证最终一致性,即使删除缓存失败,后续读请求也会回填正确数据 #### 2. 如何处理高并发下的“先删缓存再更新数据库”问题? - **参考答案**: 可能产生脏读(其他线程在数据库更新前读取旧值回填缓存) **解决方案**: 1. 使用“延迟双删”(更新数据库后延时二次删除缓存) 2. 通过数据库binlog监听(如Canal)异步更新缓存 #### 3. Cache AsideRead/Write Through模式的区别? - **参考答案**: - **Cache Aside**:应用层直接管理缓存和数据库 - **Read/Write Through**:缓存作为数据库代理,应用只缓存交互 - **Write Behind**:异步批量更新数据库,风险是可能丢失数据 #### 4. 如何避免缓存击穿导致数据库压力? - **参考答案**: 1. 使用互斥锁(如Redis SETNX)控制单线程回填缓存 2. 布隆过滤器拦截无效Key 3. 缓存空值(如`SET key null EX 60`) #### 5. 什么场景下Cache Aside不适用? - **参考答案**: 1. 写多读少场景:频繁删除缓存降低命中率 2. 强一致性需求:需结合分布式锁或2PC协议 3. 多级缓存架构:需额外协调各级缓存 --- ### 四、优化实践方案 #### 1. 最终一致性增强 ```java // 使用数据库事务日志监听(如MySQL Binlog + Canal) canalClient.subscribe(binlogEvent -> { if (event.isUpdate()) { redis.delete(event.getKey()); // 异步删除缓存 } }); ``` #### 2. 热点数据预加载 ```text 监控系统识别热点Key → 提前加载到缓存 → 设置永不过期+版本号控制 ``` #### 3. 降级策略 - **缓存不可用**:直接访问数据库,记录日志告警 - **数据库压力过大**:限流熔断(如Sentinel) --- ### 五、常见陷阱 1. **双写不一致** - **错误**:并行更新数据库和缓存 - **正确**:严格遵循“先DB缓存删除” 2. **缓存雪崩** - **错误**:大量缓存同时过期 - **正确**:随机化过期时间(如基础300秒±60秒随机) 3. **脑裂问题** - **场景**:主从同步延迟期间读取到旧数据 - **解决**:写后主库读、缓存设置版本号 --- 该方案在电商秒杀系统中得到验证,需结合监控(如缓存命中率、DB QPS)动态调整策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值