【线上故障排查】Redis缓存与数据库中数据不一致问题(面试题 + 3 步追问应对 + 案例分析)

一、高频面试题

问题1:Redis缓存和数据库数据不一致的常见原因有哪些?

参考答案:主要有三个常见原因。一是更新顺序问题,比如在读写并发场景下,先更新缓存后更新数据库,可能导致其他读请求拿到旧数据库数据;反过来先更新数据库再更新缓存,更新缓存前的读请求也会拿到旧数据。二是缓存失效异常,当缓存过期后,读请求从数据库加载新数据时如果写入缓存失败,就会导致缓存数据没更新。三是并发操作冲突,比如多个线程同时发现缓存失效,都去数据库读取数据并写入缓存,后写入的会覆盖先写入的,导致缓存数据比数据库旧。

第一步追问:能举个具体场景说明更新顺序问题吗?
参考答案:比如电商场景中,用户下单后需要扣减库存。如果先更新Redis缓存的库存数量,再更新数据库,此时若数据库更新失败,缓存却已经是扣减后的数值,后续读请求就会读到错误的库存数据,导致数据不一致。

第二步追问:缓存失效异常和更新顺序问题的本质区别是什么?
参考答案:缓存失效异常的核心是时间差导致的操作中断,比如缓存过期后,应用从数据库读取数据时网络延迟或写入缓存时程序报错,导致缓存未更新;而更新顺序问题的核心是操作顺序本身的逻辑缺陷,即使每个操作都成功,只要顺序不对,在并发场景下就会引发不一致。

第三步追问:如何从架构设计层面避免并发操作冲突?
参考答案:可以引入分布式锁,确保同一时间只有一个线程能操作缓存和数据库,比如用Redis的SET NX命令实现锁机制;或者采用队列化处理,将并发的读写请求按顺序排队执行,减少并行冲突。

问题2:线上排查Redis和数据库数据不一致时,通常有哪些步骤?

参考答案:第一步是数据对比校验,通过脚本定期全量或抽样对比两者的关键数据,比如用Python写脚本遍历数据库表和Redis键值,记录不一致的字段。第二步是日志分析,查看应用层、Redis和数据库的日志,定位发生不一致的时间点,比如应用是否在某个时刻频繁报错,Redis是否有连接超时记录。第三步是复现与调试,根据日志信息在测试环境模拟相同场景,观察数据更新流程是否符合预期,比如模拟高并发下的读写操作,看缓存和数据库是否同步。

第一步追问:数据对比时,全量对比和抽样对比分别适用于什么场景?
参考答案:全量对比适合数据量小或首次排查的场景,能全面发现问题,但耗时久;抽样对比适合数据量大的场景,比如按一定比例(如10%)抽取数据对比,效率更高,常用于日常巡检。如果抽样发现不一致,再扩大范围排查。

第二步追问:日志分析时,重点关注哪些类型的日志?
参考答案:重点看三类日志:一是应用日志,是否有更新缓存或数据库时的异常堆栈(如NullPointerException);二是Redis日志,是否有主从同步延迟、内存不足导致的淘汰日志;三是数据库慢查询日志,是否有更新操作执行时间过长,导致其他请求读到旧数据。

第三步追问:如果复现问题后发现是缓存更新逻辑错误,该如何优化?
参考答案:可以改用删除缓存而非更新缓存的策略,比如写操作时先更新数据库,再删除对应的Redis键,这样读请求下次会主动从数据库加载最新数据。同时,添加重试机制,如果删除缓存失败,通过消息队列异步重试,确保最终一致性。

二、真实技术案例

某电商平台在一次大促活动期间,用户反馈商品详情页展示的商品价格与下单时的价格不一致。例如,商品详情页显示价格为99元,但下单结算时价格却变成了199元。这种情况不仅严重影响用户体验,还可能引发用户投诉和信任危机。运营人员紧急反馈后,技术团队立即介入调查。

2、故障分析

2.1 业务流程梳理

该电商平台商品信息展示业务流程如下:用户访问商品详情页时,系统先从Redis缓存中查询商品信息,如果缓存中存在则直接返回;若缓存中不存在,则从MySQL数据库查询商品信息,查询到后将数据存入Redis缓存,再返回给用户。当商品价格发生变更时,业务系统会先更新MySQL数据库中的价格,再尝试更新Redis缓存。

2.2 可能原因推测

基于上述业务流程,初步推测导致数据不一致的原因可能有以下几种:

  1. 缓存更新失败:在更新商品价格时,更新MySQL数据库成功,但更新Redis缓存失败,后续用户查询时获取到的是旧的缓存数据。
  2. 并发问题:在高并发场景下,多个线程同时操作缓存和数据库,可能出现缓存和数据库更新顺序错乱,导致数据不一致。例如,一个线程正在更新数据库,另一个线程此时查询缓存,由于缓存未更新,获取到的是旧数据;而当数据库更新完成后,缓存却没有及时同步。
  3. 缓存过期:如果缓存设置了过期时间,在缓存过期瞬间,多个用户同时请求,可能导致大量请求穿透到数据库,并且在数据库查询结果写入缓存时出现顺序问题,造成部分用户获取到不一致的数据。

3、故障定位

3.1 查看日志

首先,开发人员查看业务系统日志,发现存在大量更新Redis缓存失败的异常日志。例如,在商品价格更新操作的日志中,记录了类似如下信息:

// 模拟更新Redis缓存的代码
public void updateProductPriceInRedis(Product product) {
   
   
    try {
   
   
        // 假设redisTemplate是Redis操作模板
        redisTemplate.opsForValue().set("product:" + product.getId(), JSON.toJSONString(product));
    } catch (RedisConnectionFailureException e) {
   
   
        // 捕获Redis连接失败异常并记录日志
        log.error("更新商品{}到Redis缓存失败", product.getId(), e);
    }
}

从日志中可以明确,缓存更新失败是导致数据不一致的一个重要原因。

3.2 分析并发情况

为了进一步分析是否存在并发问题,开发人员使用了Java的并发分析工具JProfiler对系统进行监控。在高并发测试环境下,发现当多个线程同时执行商品信息查询和更新操作时,确实出现了缓存和数据库更新顺序错乱的情况。例如,在某个时间点,线程A正在更新MySQL数据库中的商品价格,此时线程B查询Redis缓存,由于缓存未更新,获取到旧价格;随后线程A更新完数据库,但在更新Redis缓存之前,线程B又发起了一次查询,依然获取到旧的缓存数据,从而导致数据不一致。

3.3 检查缓存过期设置

查看商品信息缓存的过期时间设置,发现设置为30分钟。在大促期间,商品价格变动频繁,30分钟的过期时间可能导致在缓存过期时,大量用户请求同时查询数据库并更新缓存,引发数据不一致问题。通过分析缓存过期时间内的请求日志,确认了在缓存过期瞬间,确实出现了部分用户获取到不一致数据的情况。

4、解决方法

4.1 缓存更新失败解决方案

针对缓存更新失败的问题,采用重试机制。当更新Redis缓存失败时,进行多次重试,并记录重试次数和结果。代码实现如下:

public void updateProductPriceInRedisWithRetry(Product product, int maxRetry) {
   
   
    int retryCount = 0;
    boolean success = false;
    while (retryCount < maxRetry &&!success) {
   
   
        try {
   
   
            redisTemplate.opsForValue().set("product:" + product.getId(), 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员丘山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值