欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot?AOP?Redis實現(xiàn)延時雙刪功能實戰(zhàn)

 更新時間:2022年08月16日 08:22:58   作者:懶蟲蟲~  
本文主要介紹了SpringBoot?AOP?Redis實現(xiàn)延時雙刪功能實戰(zhàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

一、業(yè)務場景

在多線程并發(fā)情況下,假設有兩個數(shù)據(jù)庫修改請求,為保證數(shù)據(jù)庫與redis的數(shù)據(jù)一致性,
修改請求的實現(xiàn)中需要修改數(shù)據(jù)庫后,級聯(lián)修改Redis中的數(shù)據(jù)。
請求一:A修改數(shù)據(jù)庫數(shù)據(jù) B修改Redis數(shù)據(jù)
請求二:C修改數(shù)據(jù)庫數(shù)據(jù) D修改Redis數(shù)據(jù)
并發(fā)情況下就會存在A —> C —> D —> B的情況
(一定要理解線程并發(fā)執(zhí)行多組原子操作執(zhí)行順序是可能存在交叉現(xiàn)象的)

1、此時存在的問題

A修改數(shù)據(jù)庫的數(shù)據(jù)最終保存到了Redis中,C在A之后也修改了數(shù)據(jù)庫數(shù)據(jù)。

此時出現(xiàn)了Redis中數(shù)據(jù)和數(shù)據(jù)庫數(shù)據(jù)不一致的情況,在后面的查詢過程中就會長時間去先查Redis,從而出現(xiàn)查詢到的數(shù)據(jù)并不是數(shù)據(jù)庫中的真實數(shù)據(jù)的嚴重問題。

2、解決方案

在使用Redis時,需要保持Redis和數(shù)據(jù)庫數(shù)據(jù)的一致性,最流行的解決方案之一就是延時雙刪策略。
注意:要知道經(jīng)常修改的數(shù)據(jù)表不適合使用Redis,因為雙刪策略執(zhí)行的結果是把Redis中保存的那條數(shù)據(jù)刪除了,以后的查詢就都會去查詢數(shù)據(jù)庫。所以Redis使用的是讀遠遠大于改的數(shù)據(jù)緩存。
延時雙刪方案執(zhí)行步驟

1> 刪除緩存
2> 更新數(shù)據(jù)庫
3> 延時500毫秒 (根據(jù)具體業(yè)務設置延時執(zhí)行的時間)
4> 刪除緩存

3、為何要延時500毫秒?

這是為了我們在第二次刪除Redis之前能完成數(shù)據(jù)庫的更新操作。假象一下,如果沒有第三步操作時,有很大概率,在兩次刪除Redis操作執(zhí)行完畢之后,數(shù)據(jù)庫的數(shù)據(jù)還沒有更新,此時若有請求訪問數(shù)據(jù),便會出現(xiàn)我們一開始提到的那個問題。

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

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

二、代碼實踐

1、引入Redis和SpringBoot AOP依賴

<!-- 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>

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

ClearAndReloadCache延時雙刪注解

/**
 *延時雙刪
 **/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface ClearAndReloadCache {
    String name() default "";
}

ClearAndReloadCacheAspect延時雙刪切面

@Aspect
@Component
public class ClearAndReloadCacheAspect {

@Autowired
private StringRedisTemplate stringRedisTemplate;

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

@Pointcut("@annotation(com.pdh.cache.ClearAndReloadCache)")
public void pointCut(){

}
/**
* 環(huán)繞通知
* 環(huán)繞通知非常強大,可以決定目標方法是否執(zhí)行,什么時候執(zhí)行,執(zhí)行時是否需要替換方法參數(shù),執(zhí)行完畢是否需要替換返回值。
* 環(huán)繞通知第一個參數(shù)必須是org.aspectj.lang.ProceedingJoinPoint類型
* @param proceedingJoinPoint
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
    System.out.println("----------- 環(huán)繞通知 -----------");
    System.out.println("環(huán)繞通知的目標方法名:" + 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();//獲取自定義注解的方法對象的參數(shù)即name
    Set<String> keys = stringRedisTemplate.keys("*" + name + "*");//模糊定義key
    stringRedisTemplate.delete(keys);//模糊刪除redis的key值

    //執(zhí)行加入雙刪注解的改動數(shù)據(jù)庫的業(yè)務 即controller中的方法業(yè)務
    Object proceed = null;
    try {
        proceed = proceedingJoinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }

    //開一個線程 延遲1秒(此處是1秒舉例,可以改成自己的業(yè)務)
    // 在線程中延遲刪除  同時將業(yè)務代碼的結果返回 這樣不影響業(yè)務代碼的執(zhí)行
    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;//返回業(yè)務代碼的值
    }
}

3、application.yml

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

4、user_db.sql腳本

用于生產(chǎn)測試數(shù)據(jù)

DROP TABLE IF EXISTS `user_db`;
CREATE TABLE `user_db`  (
  `id` int(4) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_db
-- ----------------------------
INSERT INTO `user_db` VALUES (1, '張三');
INSERT INTO `user_db` VALUES (2, '李四');
INSERT INTO `user_db` VALUES (3, '王二');
INSERT INTO `user_db` VALUES (4, '麻子');
INSERT INTO `user_db` VALUES (5, '王三');
INSERT INTO `user_db` VALUES (6, '李三');

5、UserController

/**
 * 用戶控制層
 */
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/get/{id}")
    @Cache(name = "get method")
    //@Cacheable(cacheNames = {"get"})
    public Result get(@PathVariable("id") Integer id){
        return userService.get(id);
    }

    @PostMapping("/updateData")
    @ClearAndReloadCache(name = "get method")
    public Result updateData(@RequestBody User user){
        return userService.update(user);
    }

    @PostMapping("/insert")
    public Result insert(@RequestBody User user){
        return userService.insert(user);
    }

    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable("id") Integer id){
        return userService.delete(id);
    }
}

6、UserService

/**
 * service層
 */
@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    public Result get(Integer id){
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId,id);
        User user = userMapper.selectOne(wrapper);
        return Result.success(user);
    }

    public Result insert(User user){
        int line = userMapper.insert(user);
        if(line > 0)
            return Result.success(line);
        return Result.fail(888,"操作數(shù)據(jù)庫失敗");
    }

    public Result delete(Integer id) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId, id);
        int line = userMapper.delete(wrapper);
        if (line > 0)
            return Result.success(line);
        return Result.fail(888, "操作數(shù)據(jù)庫失敗");
    }

    public Result update(User user){
        int i = userMapper.updateById(user);
        if(i > 0)
            return Result.success(i);
        return Result.fail(888,"操作數(shù)據(jù)庫失敗");
    }
}

三、測試驗證

1、ID=10,新增一條數(shù)據(jù)

2、第一次查詢數(shù)據(jù)庫,Redis會保存查詢結果

3、第一次訪問ID為10

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

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

數(shù)據(jù)庫和緩存不一致驗證方案:

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

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

四、代碼工程及地址

核心代碼紅色方框所示

下載地址

參考文章
數(shù)據(jù)庫面試題——redis緩存主動更新策略(延時雙刪)
redis數(shù)據(jù)一致性之延時雙刪詳解
SpringAop應用三之Aop實現(xiàn)Redis緩存雙刪(自定義注解實現(xiàn)切入)
SpringBoot整合Redis實現(xiàn)緩存(自動緩存 + 手動aop緩存)

到此這篇關于SpringBoot AOP Redis實現(xiàn)延時雙刪功能實戰(zhàn)的文章就介紹到這了,更多相關SpringBoot AOP Redis延時雙刪內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring解讀@Component和@Configuration的區(qū)別以及源碼分析

    Spring解讀@Component和@Configuration的區(qū)別以及源碼分析

    通過實例分析@Component和@Configuration注解的區(qū)別,核心在于@Configuration會通過CGLIB代理確保Bean的單例,而@Component不會,在Spring容器中,使用@Configuration注解的類會被CGLIB增強,保證了即使在同一個類中多次調(diào)用@Bean方法
    2024-10-10
  • Java使用wait() notify()方法操作共享資源詳解

    Java使用wait() notify()方法操作共享資源詳解

    這篇文章主要為大家詳細介紹了Java使用wait() notify()方法操作共享資源,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Java文件分級目錄打包下載zip的實例代碼

    Java文件分級目錄打包下載zip的實例代碼

    這篇文章主要介紹了Java文件分級目錄打包下載zip的實例代碼,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • java類訪問權限與成員訪問權限解析

    java類訪問權限與成員訪問權限解析

    這篇文章主要針對java類訪問權限與成員訪問權限進行解析,對類與成員訪問權限進行驗證,感興趣的小伙伴們可以參考一下
    2016-02-02
  • SpringBoot中使用Redis的完整實例

    SpringBoot中使用Redis的完整實例

    這篇文章主要給大家介紹了關于SpringBoot中使用Redis的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • SpringBoot HATEOAS用法簡介(入門)

    SpringBoot HATEOAS用法簡介(入門)

    這篇文章主要介紹了SpringBoot HATEOAS用法簡介(入門),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10
  • jmeter添加自定義擴展函數(shù)之圖片base64編碼示例詳解

    jmeter添加自定義擴展函數(shù)之圖片base64編碼示例詳解

    這篇文章主要介紹了jmeter添加自定義擴展函數(shù)之圖片base64編碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-01-01
  • IDEA SpringBoot項目配置熱更新的步驟詳解(無需每次手動重啟服務器)

    IDEA SpringBoot項目配置熱更新的步驟詳解(無需每次手動重啟服務器)

    這篇文章主要介紹了IDEA SpringBoot項目配置熱更新的步驟,無需每次手動重啟服務器,本文通過圖文實例代碼相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-04-04
  • 解決Java項目中request流只能獲取一次的問題

    解決Java項目中request流只能獲取一次的問題

    Java項目開發(fā)中可能存在以下幾種情況,你需要在攔截器中統(tǒng)一攔截請求和你項目里可能需要搞一個統(tǒng)一的異常處理器,這兩種情況是比較常見的,本文將給大家介紹如何解決Java項目中request流只能獲取一次的問題,需要的朋友可以參考下
    2024-02-02
  • Spring Cloud Alibaba配置多環(huán)境管理詳解與實戰(zhàn)代碼

    Spring Cloud Alibaba配置多環(huán)境管理詳解與實戰(zhàn)代碼

    本文通過實際案例詳細介紹了springboot配置多環(huán)境管理的使用,以及基于nacos的配置多環(huán)境管理的實踐,在實際開發(fā)中,配置多環(huán)境管理是一個很難避開的問題,同時也是微服務治理中一個很重要的內(nèi)容,感興趣的朋友跟隨小編一起看看吧
    2024-06-06

最新評論