色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

SpringBoot AOP + Redis 延時雙刪功能實戰

jf_ro2CN3Fa ? 來源:CSDN ? 2023-10-13 16:08 ? 次閱讀


一、業務場景

在多線程并發情況下,假設有兩個數據庫修改請求,為保證數據庫與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操作執行完畢之后,數據庫的數據還沒有更新,此時若有請求訪問數據,便會出現我們一開始提到的那個問題。

4、為何要兩次刪除緩存?

如果我們沒有第二次刪除操作,此時有請求訪問數據,有可能是訪問的之前未做修改的Redis數據,刪除操作執行后,Redis為空,有請求進來時,便會去訪問數據庫,此時數據庫中的數據已是更新后的數據,保證了數據的一致性。

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

二、代碼實踐

1、引入Redis和SpringBoot AOP依賴



org.springframework.boot
spring-boot-starter-data-redis



org.springframework.boot
spring-boot-starter-aop

2、編寫自定義aop注解和切面

ClearAndReloadCache延時雙刪注解

/**
*延時雙刪
**/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public@interfaceClearAndReloadCache{
Stringname()default"";
}

ClearAndReloadCacheAspect延時雙刪切面

@Aspect
@Component
publicclassClearAndReloadCacheAspect{

@Autowired
privateStringRedisTemplatestringRedisTemplate;

/**
*切入點
*切入點,基于注解實現的切入點加上該注解的都是Aop切面的切入點
*
*/

@Pointcut("@annotation(com.pdh.cache.ClearAndReloadCache)")
publicvoidpointCut(){

}
/**
*環繞通知
*環繞通知非常強大,可以決定目標方法是否執行,什么時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換返回值。
*環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型
*@paramproceedingJoinPoint
*/
@Around("pointCut()")
publicObjectaroundAdvice(ProceedingJoinPointproceedingJoinPoint){
System.out.println("-----------環繞通知-----------");
System.out.println("環繞通知的目標方法名:"+proceedingJoinPoint.getSignature().getName());

Signaturesignature1=proceedingJoinPoint.getSignature();
MethodSignaturemethodSignature=(MethodSignature)signature1;
MethodtargetMethod=methodSignature.getMethod();//方法對象
ClearAndReloadCacheannotation=targetMethod.getAnnotation(ClearAndReloadCache.class);//反射得到自定義注解的方法對象

Stringname=annotation.name();//獲取自定義注解的方法對象的參數即name
Setkeys=stringRedisTemplate.keys("*"+name+"*");//模糊定義key
stringRedisTemplate.delete(keys);//模糊刪除redis的key值

//執行加入雙刪注解的改動數據庫的業務即controller中的方法業務
Objectproceed=null;
try{
proceed=proceedingJoinPoint.proceed();
}catch(Throwablethrowable){
throwable.printStackTrace();
}

//開一個線程延遲1秒(此處是1秒舉例,可以改成自己的業務)
//在線程中延遲刪除同時將業務代碼的結果返回這樣不影響業務代碼的執行
newThread(()->{
try{
Thread.sleep(1000);
Setkeys1=stringRedisTemplate.keys("*"+name+"*");//模糊刪除
stringRedisTemplate.delete(keys1);
System.out.println("-----------1秒鐘后,在線程中延遲刪除完畢-----------");
}catch(InterruptedExceptione){
e.printStackTrace();
}
}).start();

returnproceed;//返回業務代碼的值
}
}

3、application.yml

server:
port:8082

spring:
#redissetting
redis:
host:localhost
port:6379

#cachesetting
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


>基于SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element實現的后臺管理系統+用戶小程序,支持RBAC動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
>
>*項目地址:<https://github.com/YunaiV/yudao-cloud>
>*視頻教程:<https://doc.iocoder.cn/video/>

#mpsetting
mybatis-plus:
mapper-locations:classpath*:com/pdh/mapper/*.xml
global-config:
db-config:
table-prefix:
configuration:
#logofsql
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
#hump
map-underscore-to-camel-case:true

4、user_db.sql腳本

用于生產測試數據

DROPTABLEIFEXISTS`user_db`;
CREATETABLE`user_db`(
`id`int(4)NOTNULLAUTO_INCREMENT,
`username`varchar(32)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULL,
PRIMARYKEY(`id`)USINGBTREE
)ENGINE=InnoDBAUTO_INCREMENT=8CHARACTERSET=utf8COLLATE=utf8_general_ciROW_FORMAT=Dynamic;

------------------------------
--Recordsofuser_db
------------------------------
INSERTINTO`user_db`VALUES(1,'張三');
INSERTINTO`user_db`VALUES(2,'李四');
INSERTINTO`user_db`VALUES(3,'王二');
INSERTINTO`user_db`VALUES(4,'麻子');
INSERTINTO`user_db`VALUES(5,'王三');
INSERTINTO`user_db`VALUES(6,'李三');

5、UserController

/**
*用戶控制層
*/
@RequestMapping("/user")
@RestController
publicclassUserController{
@Autowired
privateUserServiceuserService;

@GetMapping("/get/{id}")
@Cache(name="getmethod")
//@Cacheable(cacheNames={"get"})
publicResultget(@PathVariable("id")Integerid){
returnuserService.get(id);
}

@PostMapping("/updateData")
@ClearAndReloadCache(name="getmethod")
publicResultupdateData(@RequestBodyUseruser){
returnuserService.update(user);
}

@PostMapping("/insert")
publicResultinsert(@RequestBodyUseruser){
returnuserService.insert(user);
}

@DeleteMapping("/delete/{id}")
publicResultdelete(@PathVariable("id")Integerid){
returnuserService.delete(id);
}
}

6、UserService

/**
*service層
*/
@Service
publicclassUserService{

@Resource
privateUserMapperuserMapper;

publicResultget(Integerid){
LambdaQueryWrapperwrapper=newLambdaQueryWrapper<>();
wrapper.eq(User::getId,id);
Useruser=userMapper.selectOne(wrapper);
returnResult.success(user);
}

publicResultinsert(Useruser){
intline=userMapper.insert(user);
if(line>0)
returnResult.success(line);
returnResult.fail(888,"操作數據庫失敗");
}

publicResultdelete(Integerid){
LambdaQueryWrapperwrapper=newLambdaQueryWrapper<>();
wrapper.eq(User::getId,id);
intline=userMapper.delete(wrapper);
if(line>0)
returnResult.success(line);
returnResult.fail(888,"操作數據庫失敗");
}

publicResultupdate(Useruser){
inti=userMapper.updateById(user);
if(i>0)
returnResult.success(i);
returnResult.fail(888,"操作數據庫失敗");
}
}

三、測試驗證

1、ID=10,新增一條數據

1ab9e8f0-699e-11ee-939d-92fbcf53809c.jpg

2、第一次查詢數據庫,Redis會保存查詢結果

1acd49a4-699e-11ee-939d-92fbcf53809c.jpg

3、第一次訪問ID為10

1ae5f3b4-699e-11ee-939d-92fbcf53809c.jpg

4、第一次訪問數據庫ID為10,將結果存入Redis

1b1661e8-699e-11ee-939d-92fbcf53809c.jpg

5、更新ID為10對應的用戶名(驗證數據庫和緩存不一致方案)

1b28ddb4-699e-11ee-939d-92fbcf53809c.jpg

數據庫和緩存不一致驗證方案:

打個斷點,模擬A線程執行第一次刪除后,在A更新數據庫完成之前,另外一個線程B訪問ID=10,讀取的還是舊數據。

1b33ac9e-699e-11ee-939d-92fbcf53809c.png1b415498-699e-11ee-939d-92fbcf53809c.jpg

6、采用第二次刪除,根據業務場景設置延時時間,兩次刪除緩存成功后,Redis結果為空。讀取的都是數據庫真實數據,不會出現讀緩存和數據庫不一致情況。

1b48ce6c-699e-11ee-939d-92fbcf53809c.jpg

四、代碼工程及地址

核心代碼紅色方框所示

?

https://gitee.com/jike11231/redisDemo.git

?

1b58a9d6-699e-11ee-939d-92fbcf53809c.png


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 數據庫
    +關注

    關注

    7

    文章

    3826

    瀏覽量

    64509
  • Redis
    +關注

    關注

    0

    文章

    376

    瀏覽量

    10888
  • SpringBoot
    +關注

    關注

    0

    文章

    173

    瀏覽量

    184

原文標題:SpringBoot AOP + Redis 延時雙刪功能實戰

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Spring AOP如何破解java應用

    預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,從另一視角擴展了對面向對象編程的形式。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度
    的頭像 發表于 09-25 11:16 ?906次閱讀
    Spring <b class='flag-5'>AOP</b>如何破解java應用

    MySQL與Redis延遲策略

    背景 在當前環境下,通常我們會首選redis緩存來減輕我們數據庫訪問壓力。但是也會遇到以下這種情況:大量用戶來訪問我們系統,首先會去查詢緩存, 如果緩存中沒有數據,則去查詢數據庫,然后更新數據到緩存
    的頭像 發表于 09-25 14:28 ?926次閱讀
    MySQL與<b class='flag-5'>Redis</b>延遲<b class='flag-5'>雙</b><b class='flag-5'>刪</b>策略

    求...

    本帖最后由 871881392 于 2014-11-28 08:21 編輯 求
    發表于 11-24 14:56

    Redis Stream應用案例

    的基本使用介紹和設計理念可以看我之前的一篇文章(Redis Stream簡介)。Redis Stream本質上是在Redis內核上(非Redis Module)實現的一個消息發布訂閱
    發表于 06-26 17:15

    Spring boot中Redis的使用

    【本人禿頂程序員】springboot專輯:Spring boot中Redis的使用
    發表于 03-27 11:42

    Java程序員筆記之mybatis結合redis實戰二級緩存

    Java程序員筆記——mybatis結合redis實戰二級緩存
    發表于 06-10 09:15

    怎樣去使用springboot

    怎樣去使用springboot呢?學習springboot需要懂得哪些?
    發表于 10-25 07:13

    觸摸、聲控功能延時燈電路圖

    觸摸、聲控功能延時燈電路圖
    發表于 05-25 14:02 ?1796次閱讀
    觸摸、聲控<b class='flag-5'>雙</b><b class='flag-5'>功能</b><b class='flag-5'>延時</b>燈電路圖

    Springboot+redis操作多種實現

    一、Jedis,Redisson,Lettuce三者的區別共同點:都提供了基于Redis操作的Java API,只是封裝程度,具體實現稍有不同。 不同點: 1.1、Jedis 是Redis的Java
    的頭像 發表于 09-22 10:48 ?1845次閱讀
    <b class='flag-5'>Springboot+redis</b>操作多種實現

    基于SpringBoot+Redis的轉盤抽獎

    基于SpringBoot+Redis等技術實現轉盤抽獎活動項目,含前端、后臺及數據庫文件
    的頭像 發表于 02-28 14:24 ?1553次閱讀
    基于<b class='flag-5'>SpringBoot+Redis</b>的轉盤抽獎

    什么是 SpringBoot

    本文從為什么要有 `SpringBoot`,以及 `SpringBoot` 到底方便在哪里開始入手,逐步分析了 `SpringBoot` 自動裝配的原理,最后手寫了一個簡單的 `start` 組件,通過
    的頭像 發表于 04-07 11:28 ?1326次閱讀
    什么是 <b class='flag-5'>SpringBoot</b>?

    如何在SpringBoot中解決Redis的緩存穿透等問題

    今天給大家介紹一下如何在SpringBoot中解決Redis的緩存穿透、緩存擊穿、緩存雪崩的問題。
    的頭像 發表于 04-28 11:35 ?739次閱讀

    如何用Springboot整合Redis

    本篇文件我們來介紹如何用Springboot整合Redis。 1、Docker 安裝 Redis 1.1 下載鏡像 docker pull redis: 6 . 2 . 6 1.2 創
    的頭像 發表于 10-08 14:56 ?593次閱讀
    如何用<b class='flag-5'>Springboot</b>整合<b class='flag-5'>Redis</b>

    AOP要怎么使用

    AOP(Aspect-Oriented Programming)經常會出現在面試過程中,AOP到底有沒有用,要怎么使用呢。本篇來一起撥開迷霧! 1 第一個AOP示例 我們會一次將所有的通知類型都覆蓋
    的頭像 發表于 10-09 16:18 ?692次閱讀
    <b class='flag-5'>AOP</b>要怎么使用

    一個注解搞定SpringBoot接口防刷

    技術要點:springboot的基本知識,redis基本操作,
    的頭像 發表于 11-28 10:46 ?414次閱讀
    主站蜘蛛池模板: 午夜阳光影院在线观看视频| 99成人在线| 国产精品视频免费观看| 亚洲成人综合在线| 久久综合电影| 国产精品嫩草影院| 伊人久久综在合线亚洲| 色琪琪无码成人AV视频| 久久九九青青国产精品| 97免费观看视频| 日本 稀土矿| 精品久久久麻豆国产精品 | 中文字幕在线视频在线看| 日本久久久免费高清| 精品国产高清自在线看| 56prom在线精品国产| 色偷偷男人| 美女脱内衣裸身尿口露出来| 宫交拔不出来了h黑人| gogogo高清在线观看| 亚洲一区日韩一区欧美一区a| 玩弄人妻少妇500系列网址| 麻豆国产精品久久人妻| 国产午夜在线视频| 疯狂第一次国语| 最新国产在线视频在线| 亚洲午夜久久久精品电影院 | 国产午夜久久影院| jizz破处| 亚洲午夜精品久久久久久抢| 无码人妻99久久密AV| 欧美群交XXXCOM| 免费无码又爽又黄又刺激网站| 国产亚洲美女精品久久久2020 | 无码人妻丰满熟妇啪啪网不卡| 快播理伦片| 九九热免费在线观看| 国产成人免费网站在线观看| 成人精品视频| 91成品视频| 一边亲着一面膜下奶韩剧免费|