赞
踩
在多线程并发情况下,假设有两个数据库修改请求,为保证数据库与redis的数据一致性,
修改请求的实现中需要修改数据库后,级联修改Redis中的数据。
请求一:A修改数据库数据 B修改Redis数据
请求二:C修改数据库数据 D修改Redis数据
并发情况下就会存在A —> C —> D —> B的情况
一定要理解线程并发执行多组原子操作执行顺序是可能存在交叉现象的
A修改数据库的数据最终保存到了Redis中,C在A之后也修改了数据库数据。 此时出现了Redis中数据和数据库数据不一致的情况,在后面的查询过程中就会长时间去先查Redis, 从而出现查询到的数据并不是数据库中的真实数据的严重问题。
在使用Redis时,需要保持Redis和数据库数据的一致性,最流行的解决方案之一就是延时双删策略。
注意:要知道经常修改的数据表不适合使用Redis,因为双删策略执行的结果是把Redis中保存的那条数据删除了,以后的查询就都会去查询数据库。所以Redis使用的是读远远大于改的数据缓存。
延时双删方案执行步骤
1> 删除缓存
2> 更新数据库
3> 延时500毫秒 (根据具体业务设置延时执行的时间)
4> 删除缓存
这是为了我们在第二次删除Redis之前能完成数据库的更新操作。假象一下,如果没有第三步操作时,有很大概率,在两次删除Redis操作执行完毕之后,数据库的数据还没有更新,此时若有请求访问数据,便会出现我们一开始提到的那个问题。
插播一条:如果你近期准备面试跳槽,建议在ddkk.com在线刷题,涵盖 一万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题,还有市面上最全的技术五百套,精品系列教程,免费提供。
如果我们没有第二次删除操作,此时有请求访问数据,有可能是访问的之前未做修改的Redis数据,删除操作执行后,Redis为空,有请求进来时,便会去访问数据库,此时数据库中的数据已是更新后的数据,保证了数据的一致性。
- <!-- redis使用 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!-- aop -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
ClearAndReloadCache延时双删注解
- /**
- *延时双删
- **/
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Target(ElementType.METHOD)
- public @interface ClearAndReloadCache {
-
-
- String name() default "";
- }
ClearAndReloadCacheAspect延时双删切面
- @Aspect
- @Component
- public class ClearAndReloadCacheAspect {
-
-
-
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
-
- /**
- * 切入点
- *切入点,基于注解实现的切入点 加上该注解的都是Aop切面的切入点
- *
- */
-
- @Pointcut("@annotation(com.pdh.cache.ClearAndReloadCache)")
- public void pointCut(){
-
-
-
- }
- /**
- * 环绕通知
- * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
- * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
- * @param proceedingJoinPoint
- */
- @Around("pointCut()")
- public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
-
-
- System.out.println("----------- 环绕通知 -----------");
- System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
-
- Signature signature1 = proceedingJoinPoint.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature1;
- Method targetMethod = methodSignature.getMethod();//方法对象
- ClearAndReloadCache annotation = targetMethod.getAnnotation(ClearAndReloadCache.class);//反射得到自定义注解的方法对象
-
- String name = annotation.name();//获取自定义注解的方法对象的参数即name
- Set<String> keys = stringRedisTemplate.keys("*" + name + "*");//模糊定义key
- stringRedisTemplate.delete(keys);//模糊删除redis的key值
-
- //执行加入双删注解的改动数据库的业务 即controller中的方法业务
- Object proceed = null;
- try {
-
-
- proceed = proceedingJoinPoint.proceed();
- } catch (Throwable throwable) {
-
-
- throwable.printStackTrace();
- }
-
- //开一个线程 延迟1秒(此处是1秒举例,可以改成自己的业务)
- // 在线程中延迟删除 同时将业务代码的结果返回 这样不影响业务代码的执行
- new Thread(() -> {
-
-
- try {
-
-
- Thread.sleep(1000);
- Set<String> keys1 = stringRedisTemplate.keys("*" + name + "*");//模糊删除
- stringRedisTemplate.delete(keys1);
- System.out.println("-----------1秒钟后,在线程中延迟删除完毕 -----------");
- } catch (InterruptedException e) {
-
-
- e.printStackTrace();
- }
- }).start();
-
- return proceed;//返回业务代码的值
- }
- }

- server:
- port: 8082
-
- spring:
- redis setting
- redis:
- host: localhost
- port: 6379
-
- cache setting
- cache:
- redis:
- time-to-live: 60000 60s
-
- datasource:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3306/test
- username: root
- password: 1234
-
- # mp setting
- mybatis-plus:
- mapper-locations: classpath*:com/pdh/mapper/*.xml
- global-config:
- db-config:
- table-prefix:
- configuration:
- log of sql
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- hump
- map-underscore-to-camel-case: true

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。