redis作為用的非常多的緩存數(shù)據(jù)庫(kù),在多線程場(chǎng)景下,可能會(huì)出現(xiàn)數(shù)據(jù)庫(kù)與redis數(shù)據(jù)不一致的現(xiàn)象
數(shù)據(jù)不一致的現(xiàn)象:https://blog.csdn.net/m0_73700925/article/details/133447466
這里采用aop+redis來(lái)解決這個(gè)方法:
- 刪除緩存
- 更新數(shù)據(jù)庫(kù)
- 延時(shí)一定時(shí)間,比如500ms
- 刪除緩存
這里之所以要延時(shí)一段時(shí)間再刪除,是為了避免多線程情況下,更新數(shù)據(jù)庫(kù)的操作還沒執(zhí)行,就執(zhí)行了第二次刪除緩存的操作,此時(shí)如果有請(qǐng)求進(jìn)來(lái),就會(huì)讀取數(shù)據(jù)庫(kù)并將數(shù)據(jù)寫入緩存,這時(shí)再更新數(shù)據(jù)庫(kù)就會(huì)導(dǎo)致數(shù)據(jù)不一致的問題
兩次刪除緩存是因?yàn)榈谝淮蝿h除緩存后,這時(shí)如果有請(qǐng)求進(jìn)來(lái),得到了數(shù)據(jù)并寫入redis,然后再更新數(shù)據(jù)庫(kù),就會(huì)導(dǎo)致數(shù)據(jù)不一致
- 自定義注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface ClearAndReloadCache {
String name() default "";
}
- 編寫切面,以自定義注解作為切入點(diǎn)
@Aspect
@Component
public class ClearAndReloadCacheAspect {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Around("@annotation(clearAndReloadCache)")
public Object innerAround(ProceedingJoinPoint proceedingJoinPoint, ClearAndReloadCache clearAndReloadCache) throws Throwable {
System.out.println("----------- 環(huán)繞通知 -----------");
System.out.println("環(huán)繞通知的目標(biāo)方法名:" + proceedingJoinPoint.getSignature().getName());
Signature signature1 = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature)signature1;
Method targetMethod = methodSignature.getMethod();//方法對(duì)象
ClearAndReloadCache annotation = targetMethod.getAnnotation(ClearAndReloadCache.class);//反射得到自定義注解的方法對(duì)象
String name = annotation.name();
// 延時(shí)雙刪中的第一次刪除緩存
Set<String> keys = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys);
Object proceed = null;
// 執(zhí)行業(yè)務(wù)層代碼
proceed = proceedingJoinPoint.proceed();
// 執(zhí)行延遲雙刪中的第二次刪除緩存
// 開啟新線程是為了避免主線程堵塞等待
new Thread(() -> {
try {
Thread.sleep(1000);
Set<String> keys2 = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
return proceed;
}
}
切面也可以寫成這樣,更方便理解文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-795907.html
@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("----------- 環(huán)繞通知 -----------");
System.out.println("環(huán)繞通知的目標(biāo)方法名:" + proceedingJoinPoint.getSignature().getName());
Signature signature1 = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature)signature1;
Method targetMethod = methodSignature.getMethod();//方法對(duì)象
ClearAndReloadCache annotation = targetMethod.getAnnotation(ClearAndReloadCache.class);//反射得到自定義注解的方法對(duì)象
String name = annotation.name();
// 延時(shí)雙刪中的第一次刪除緩存
Set<String> keys = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys);
Object proceed = null;
// 執(zhí)行業(yè)務(wù)層代碼
proceed = proceedingJoinPoint.proceed();
// 執(zhí)行延遲雙刪中的第二次刪除緩存
// 開啟新線程是為了避免主線程堵塞等待
new Thread(() -> {
try {
Thread.sleep(1000);
Set<String> keys2 = stringRedisTemplate.keys("*" + name + "*");
stringRedisTemplate.delete(keys2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
return proceed;
}
}
- 業(yè)務(wù)層代碼
controller
@ApiOperation("小程序小程序平臺(tái)通用-工單詳情")
@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()中將查到的數(shù)據(jù)存入了redis中,redisService.set("getById"+id, json);
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-795907.html
- 測(cè)試
首先需要通過idea多開啟一個(gè)程序,用于模擬多線程
然后通過打斷點(diǎn)的方式
- 首先調(diào)用查詢接口,此時(shí)會(huì)將數(shù)據(jù)存入redis中
- 然后調(diào)用修改接口,進(jìn)入debug模式,當(dāng)?shù)谝淮蝿h除緩存后,不要往下走
- 再次調(diào)用查詢接口,用于模擬多線程情況下的數(shù)據(jù)不一致情況
- 這時(shí)redis又會(huì)存入數(shù)據(jù)
- 接著就是更新數(shù)據(jù)庫(kù)的操作
- 此時(shí)如果沒有第二次刪除緩存,就會(huì)出現(xiàn)數(shù)據(jù)不一致了
- 所以第二次刪除緩存是很有必要的
到了這里,關(guān)于springboot自定義注解+aop+redis實(shí)現(xiàn)延時(shí)雙刪的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!