redis作为用的非常多的缓存数据库,在多线程场景下,可能会出现数据库与redis数据不一致的现象
数据不一致的现象:https://blog.csdn.net/m0_73700925/article/details/133447466
这里采用aop+redis来解决这个方法:
这里之所以要延时一段时间再删除,是为了避免多线程情况下,更新数据库的操作还没执行,就执行了第二次删除缓存的操作,此时如果有请求进来,就会读取数据库并将数据写入缓存,这时再更新数据库就会导致数据不一致的问题
两次删除缓存是因为第一次删除缓存后,这时如果有请求进来,得到了数据并写入redis,然后再更新数据库,就会导致数据不一致
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface ClearAndReloadCache {
String name() default "";
}
@Aspect
@Component
public class ClearAndReloadCacheAspect {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Around("@annotation(clearAndReloadCache)")
public Object innerAround(ProceedingJoinPoint proceedingJoinPoint, ClearAndReloadCache clearAndReloadCache) throws Throwable {
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();
// 延时双删中的第一次删除缓存
Set<String> keys = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys);
Object proceed = null;
// 执行业务层代码
proceed = proceedingJoinPoint.proceed();
// 执行延迟双删中的第二次删除缓存
// 开启新线程是为了避免主线程堵塞等待
new Thread(() -> {
try {
Thread.sleep(1000);
Set<String> keys2 = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
return proceed;
}
}
切面也可以写成这样,更方便理解
@Aspect
@Component
public class ClearAndReloadCacheAspect {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Pointcut("@annotation(com.toptolink.iot.permission.annotation.ClearAndReloadCache)")
public void pointCut(){
}
@Around("pointCut()")
public Object innerAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
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();
// 延时双删中的第一次删除缓存
Set<String> keys = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys);
Object proceed = null;
// 执行业务层代码
proceed = proceedingJoinPoint.proceed();
// 执行延迟双删中的第二次删除缓存
// 开启新线程是为了避免主线程堵塞等待
new Thread(() -> {
try {
Thread.sleep(1000);
Set<String> keys2 = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
return proceed;
}
}
@ApiOperation("小程序小程序平台通用-工单详情")
@GetMapping("/queryWorkOrderDetail")
@PreAuthorize(Permissions.YQZ_MAINTAIN)
public DataResponseBody queryWorkOrderDetail(@RequestParam Long id) {
return new DataResponseBody(iMaintainService.queryWorkOrderDetail(id));
}
/**
* @return
*/
@GetMapping("/TODOupdateById")
@PreAuthorize(Permissions.YQZ_MAINTAIN)
@ClearAndReloadCache(name = "getById")
public DataResponseBody TODOupdateById(Long id, String customerFullName) {
return new DataResponseBody(iMaintainService.TODOupdateById(id, customerFullName));
}
我在queryWorkOrderDetail()中将查到的数据存入了redis中,redisService.set("getById"+id, json);