ToB企服应用市场:ToB评测及商务社交产业平台

标题: 缓存与数据库一致性——延迟双删 [打印本页]

作者: 没腿的鸟    时间: 2024-12-26 14:40
标题: 缓存与数据库一致性——延迟双删
一、延时双删是什么?
        延迟双删(Delay Double Delete)是一种在数据更新或删除时为了保证数据一致性而采取的策略。这种策略通常用于解决数据在缓存和数据库中不一致的标题。
        数据一致性:缓存和数据库一致性标题是指在应用体系中,缓存层(通常是内存中的缓存,如Redis、Memcached等)和数据库层(长期化存储,如MySQL、PostgreSQL等)之间的数据状态不一致的情况。这种情况通常发生在数据更新操作后,缓存中的数据没有实时更新或删除,导致用户读取到的数据不是最新的,从而影响体系的正确性和用户体验。
二、解决办法
那么我们该如何解决数据一致性标题呢?
首先我们需要明白数据一致性标题的重要原因是什么,从重要原因入手才是解决标题的关键!
数据一致性的根本原因是 缓存和数据库中的数据差别步,那么我们该如何让 缓存 和 数据库 中的数据尽大概的即时同步?这就需要选择一个比力好的缓存更新策略了
常见的缓存更新策略:
内存淘汰(全自动)。利用Redis的内存淘汰机制实现缓存更新,Redis的内存淘汰机制是当Redis发现内存不敷时,会根据肯定的策略自动淘汰部门数据
超时剔除(半自动)。手动给缓存数据添加TTL,到期后Redis自动删除缓存
主动更新(手动)。手动编码实现缓存更新,在修改数据库的同时更新缓存
       双写方案(Cache Aside Pattern):人工编码方式,缓存调用者在更新完数据库后再去更新缓存。利用困难,灵活度高。
                1)读取(Read):当需要读取数据时,首先检查缓存是否存在该数据。如果缓存中存在,直接返回缓存中的数据。如果缓存中不存在,则从底层数据存储(如数据库)中获取数据,并将数据存储到缓存中,以便以后的读取操作可以更快地访问该数据。
                2)写入(Write):当进行数据写入操作时,首先更新底层数据存储中的数据。然后,根据详细情况,可以选择直接更新缓存中的数据(使缓存与底层数据存储保持同步),或者是简朴地将缓存中与修改数据相关的条目标记为无效状态(缓存失效),以便下一次读取时重新加载最新数据
         利用双写方案需要思量以下几个标题: 是利用更新缓存模式还是利用删除缓存模式?
          更新缓存模式:每次更新数据库都更新缓存,无效写操作较多(不推荐利用) 如果我们执行上百次更新数据库操作,那么就要执行上百次写入缓存的操作,而在这期间并没有查询请求,那么这上百次写入缓存的操作就显得没有什么意义
           删除缓存模式:更新数据时更新数据库并删除缓存,查询时更新缓存,无效写操作较少(推荐利用) 选择利用删除缓存模式,那么是先操作缓存还是先操作数据库?
 先操作缓存:先删缓存,再更新数据库。当线程1删除缓存到更新数据库之间的时间段,会有其它线程进来查询数据,由于没有加锁,且前面的线程将缓存删除了,这就导致请求会直接打到数据库上,给数据库带来巨大压力。这个变乱发生的概率很大,因为缓存的读写速度块,而数据库的读写较慢。 这种方式的不敷之处:存在缓存击穿标题,且概率较大  
先操作数据库:先更新数据库,再删缓存。 当线程1在查询缓存且未命中,此时线程1查询数据,查询完准备写入缓存时,由于没有加锁线程2攻其不备,线程2在这期间对数据库进行了更新,此时线程1将旧数据返回了,出现了脏读,这个变乱发生的概率很低,因为先是需要满足缓存未命中,且在写入缓存的那段变乱内有一个线程进行更新操作,缓存的查询很快,这段清闲时间很小,以是出现脏读现象的概率也很低。
值得留意的是,不管哪种方案,都制止不了Redis存在脏数据的标题,只能减轻这个标题,要想彻底解决,得要用到同步锁和对应的业务逻辑层面解决。
延时双删实现 在前面先容到,先更新数据库后删Redis缓存是一致性相对最高的。这是就有人举手了:我就想要先删缓存怎么办?这时延时双删就出现了,针对「先删除缓存,再更新数据库」方案在「读 + 写」并发请求而造成缓存不一致的解决办法是「延迟双删」。
三、延时双删的实现
  1.     /**
  2.      * 冻结用户
  3.      * @author Java小牛马
  4.      * @param userId
  5.      * @return
  6.      */
  7.     @Transactional(rollbackFor = Exception.class)
  8.     public UserOperatorResponse freeze(Long userId) {
  9.         UserOperatorResponse userOperatorResponse = new UserOperatorResponse();
  10.         User user = userMapper.findById(userId);
  11.         Assert.notNull(user, () -> new UserException(USER_NOT_EXIST));
  12.         Assert.isTrue(user.getState() == UserStateEnum.ACTIVE, () -> new UserException(USER_STATUS_IS_NOT_ACTIVE));
  13.         //第一次删除缓存
  14.         idUserCache.remove(user.getId().toString());
  15.         if (user.getState() == UserStateEnum.FROZEN) {
  16.             userOperatorResponse.setSuccess(true);
  17.             return userOperatorResponse;
  18.         }
  19.         user.setState(UserStateEnum.FROZEN);
  20.         // 更新数据库
  21.         boolean updateResult = updateById(user);
  22.         Assert.isTrue(updateResult, () -> new BizException(RepoErrorCode.UPDATE_FAILED));
  23.         //加入流水
  24.         long result = userOperateStreamService.insertStream(user, UserOperateTypeEnum.FREEZE);
  25.         Assert.notNull(result, () -> new BizException(RepoErrorCode.UPDATE_FAILED));
  26.         //第二次删除缓存
  27.         userCacheDelayDeleteService.delayedCacheDelete(idUserCache, user);
  28.         userOperatorResponse.setSuccess(true);
  29.         return userOperatorResponse;
  30.     }
复制代码
这里做一个详细先容: 首先,代码先删除了 Redis 中的缓存数据,以确保接下来的读取操作会从数据库中读取最新的数据。 接着,代码更新了数据库中的数据,将数据更新为最新的值。 在此之后,代码让当前线程休眠一段时间N,这个时间段是为了给数据库操作充足的时间来完成,确保数据已经长期化到数据库中。 最后,代码再次删除 Redis 中的缓存数据。这里是延迟双删的关键步骤。由于之前已经删除了缓存数据,再次删除的目的是为了防止在 Thread.sleep(N) 的时间内有其他线程读取到旧的缓存数据。因为在这段时间内,缓存数据已经被清空,以是其他线程在读取数据时会发现缓存中不存在,然后从数据库中读取最新的数据并写入缓存,从而保证了数据的一致性。 需要留意的是,这种延迟双删策略并不能完全保证数据的一致性。 如果在 Thread.sleep(N) 的时间内发生了其他线程的写入操作,并且将新数据写入了缓存中,那么在第二次删除缓存时,会将这个新数据从缓存中删除,大概导致缓存和数据库中的数据不一致。 因此,延迟双删策略只能在肯定程度上进步数据一致性的概率,但不能完全解决数据一致性的标题。更加严酷的数据一致性保证需要利用更复杂的机制,比如利用消息队列等。
四、为什么要利用延时双删
        在延时双删策略中,当需要更新数据库中的数据时,首先会先删除缓存,然后再进行数据库的更新操作。如许做的目的是为了制止在数据库更新的过程中,有其他请求读取了已经失效的缓存数据。 通过延时双删策略,可以保证在数据库更新期间,其他读取请求在缓存不命中的情况下,会直接读取数据库的最新数据,而不会读取到已经失效的缓存数据。如许就保证了数据的一致性和缓存的即时更新。 延时双删策略固然会增加一次缓存删除的开销,但是可以有效地进步数据的一致性,并且在高并发读取的场景下,减轻数据库的读取压力,进步读取性能和响应速度。
五、方案选择
         延时双删实用于对数据一致性要求较高的场景。它可以或许保证在数据库更新期间,读取请求不会读取到已经失效的缓存数据,从而保证数据的一致性。但是它需要进行两次缓存删除操作,大概会增加肯定的资源开销;
        先更新数据库后删除缓存实用于对一致性要求较低,对性能要求较高的场景。它可以或许淘汰一次缓存删除的开销,但是在数据库更新期间,读取请求大概会读取到已经失效的缓存数据,从而导致数据不一致。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4