当前位置:   article > 正文

Redis 延时双删_redis延迟双删实现

redis延迟双删实现

一、业务场景

在多线程并发情况下,假设有两个数据库修改请求,为保证数据库与redis的数据一致性

修改请求的实现中需要修改数据库后,级联修改Redis中的数据。

请求一:A修改数据库数据 B修改Redis数据
请求二:C修改数据库数据 D修改Redis数据

并发情况下就会存在A —> C —> D —> B的情况

一定要理解线程并发执行多组原子操作执行顺序是可能存在交叉现象的

1、此时存在的问题

A修改数据库的数据最终保存到了Redis中,C在A之后也修改了数据库数据。 此时出现了Redis中数据和数据库数据不一致的情况,在后面的查询过程中就会长时间去先查Redis, 从而出现查询到的数据并不是数据库中的真实数据的严重问题。

2、解决方案

在使用Redis时,需要保持Redis和数据库数据的一致性,最流行的解决方案之一就是延时双删策略。


注意:要知道经常修改的数据表不适合使用Redis,因为双删策略执行的结果是把Redis中保存的那条数据删除了,以后的查询就都会去查询数据库。所以Redis使用的是读远远大于改的数据缓存。

延时双删方案执行步骤

1> 删除缓存
2> 更新数据库
3> 延时500毫秒 (根据具体业务设置延时执行的时间)
4> 删除缓存

3、为何要延时500毫秒?

这是为了我们在第二次删除Redis之前能完成数据库的更新操作。假象一下,如果没有第三步操作时,有很大概率,在两次删除Redis操作执行完毕之后,数据库的数据还没有更新,此时若有请求访问数据,便会出现我们一开始提到的那个问题。

插播一条:如果你近期准备面试跳槽,建议在ddkk.com在线刷题,涵盖 一万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题,还有市面上最全的技术五百套,精品系列教程,免费提供。

4、为何要两次删除缓存?

如果我们没有第二次删除操作,此时有请求访问数据,有可能是访问的之前未做修改的Redis数据,删除操作执行后,Redis为空,有请求进来时,便会去访问数据库,此时数据库中的数据已是更新后的数据,保证了数据的一致性。

二、SpringBoot AOP + Redis 延时双删功能

1、引入Redis和SpringBoot AOP依赖

  1. <!-- redis使用 -->
  2. <dependency>
  3.       <groupId>org.springframework.boot</groupId>
  4.       <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>
  6. <!-- aop -->
  7. <dependency>
  8.       <groupId>org.springframework.boot</groupId>
  9.       <artifactId>spring-boot-starter-aop</artifactId>
  10. </dependency>

2、编写自定义aop注解和切面

ClearAndReloadCache延时双删注解

  1. /**
  2.  *延时双删
  3.  **/
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Documented
  6. @Target(ElementType.METHOD)
  7. public @interface ClearAndReloadCache {
  8.    
  9.      
  10.     String name() default "";
  11. }

3、切面实现

ClearAndReloadCacheAspect延时双删切面

  1. @Aspect
  2. @Component
  3. public class ClearAndReloadCacheAspect {
  4.    
  5.      
  6. @Autowired
  7. private StringRedisTemplate stringRedisTemplate;
  8. /**
  9. * 切入点
  10. *切入点,基于注解实现的切入点  加上该注解的都是Aop切面的切入点
  11. *
  12. */
  13. @Pointcut("@annotation(com.pdh.cache.ClearAndReloadCache)")
  14. public void pointCut(){
  15.    
  16.      
  17. }
  18. /**
  19. * 环绕通知
  20. * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
  21. * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
  22. * @param proceedingJoinPoint
  23. */
  24. @Around("pointCut()")
  25. public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
  26.    
  27.      
  28.     System.out.println("----------- 环绕通知 -----------");
  29.     System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
  30.     Signature signature1 = proceedingJoinPoint.getSignature();
  31.     MethodSignature methodSignature = (MethodSignature)signature1;
  32.     Method targetMethod = methodSignature.getMethod();//方法对象
  33.     ClearAndReloadCache annotation = targetMethod.getAnnotation(ClearAndReloadCache.class);//反射得到自定义注解的方法对象
  34.     String name = annotation.name();//获取自定义注解的方法对象的参数即name
  35.     Set<String> keys = stringRedisTemplate.keys("*" + name + "*");//模糊定义key
  36.     stringRedisTemplate.delete(keys);//模糊删除redis的key
  37.     //执行加入双删注解的改动数据库的业务 即controller中的方法业务
  38.     Object proceed = null;
  39.     try {
  40.    
  41.      
  42.         proceed = proceedingJoinPoint.proceed();
  43.     } catch (Throwable throwable) {
  44.    
  45.      
  46.         throwable.printStackTrace();
  47.     }
  48.     //开一个线程 延迟1秒(此处是1秒举例,可以改成自己的业务)
  49.     // 在线程中延迟删除  同时将业务代码的结果返回 这样不影响业务代码的执行
  50.     new Thread(() -> {
  51.    
  52.      
  53.         try {
  54.    
  55.      
  56.             Thread.sleep(1000);
  57.             Set<String> keys1 = stringRedisTemplate.keys("*" + name + "*");//模糊删除
  58.             stringRedisTemplate.delete(keys1);
  59.             System.out.println("-----------1秒钟后,在线程中延迟删除完毕 -----------");
  60.         } catch (InterruptedException e) {
  61.    
  62.      
  63.             e.printStackTrace();
  64.         }
  65.     }).start();
  66.     return proceed;//返回业务代码的值
  67.     }
  68. }

 

4、application.yml

  1. server:
  2.   port: 8082
  3. spring:
  4.   redis setting
  5.   redis:
  6.     host: localhost
  7.     port: 6379
  8.   cache setting
  9.   cache:
  10.     redis:
  11.       time-to-live: 60000 60s
  12.   datasource:
  13.     driver-class-name: com.mysql.cj.jdbc.Driver
  14.     url: jdbc:mysql://localhost:3306/test
  15.     username: root
  16.     password: 1234
  17. # mp setting
  18. mybatis-plus:
  19.   mapper-locations: classpath*:com/pdh/mapper/*.xml
  20.   global-config:
  21.     db-config:
  22.       table-prefix:
  23.   configuration:
  24.     log of sql
  25.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  26.     hump
  27.     map-underscore-to-camel-case: true

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/469178
推荐阅读
相关标签
  

闽ICP备14008679号