Heim >Datenbank >Redis >Wie SpringBoot AOP Redis die verzögerte Doppellöschfunktion implementiert

Wie SpringBoot AOP Redis die verzögerte Doppellöschfunktion implementiert

PHPz
PHPznach vorne
2023-05-29 08:53:082101Durchsuche

    1. Geschäftsszenario

    Angenommen, es gibt zwei Datenbankänderungsanforderungen, um die Datenkonsistenz zwischen der Datenbank und Redis sicherzustellen:
    Die Implementierung der Änderungsanforderung erfordert eine Kaskadierung Änderungen nach der Änderung der Datenbank in Redis.
    Anfrage 1: A ändert die Datenbankdaten B ändert die Redis-Daten
    Anfrage 2: C ändert die Datenbankdaten D ändert die Redis-Daten
    In einer gleichzeitigen Situation gibt es die Situation A —> –> , und C hat auch die Datenbank nach A-Daten geändert.

    Zu diesem Zeitpunkt besteht eine Inkonsistenz zwischen den Daten in Redis und den Datenbankdaten. Im nachfolgenden Abfrageprozess wird Redis zunächst lange überprüft, was zu einem schwerwiegenden Problem führt, da es sich bei den abgefragten Daten nicht um die tatsächlichen Daten handelt in der Datenbank.

    2. Lösung

    Bei der Verwendung von Redis müssen Sie die Konsistenz von Redis und Datenbankdaten aufrechterhalten. Eine der beliebtesten Lösungen ist die Strategie des verzögerten doppelten Löschens.

    Hinweis: Sie müssen wissen, dass häufig geänderte Datentabellen nicht für die Verwendung von Redis geeignet sind, da das Ergebnis der doppelten Löschstrategie darin besteht, die in Redis gespeicherten Daten zu löschen und nachfolgende Abfragen die Datenbank abfragen. Daher verwendet Redis einen Datencache, der weit mehr liest als ändert.

    Ausführungsschritte des verzögerten Doppellöschplans

    1> Cache löschen
    3> Verzögerung 500 Millisekunden (stellen Sie die verzögerte Ausführungszeit entsprechend dem jeweiligen Unternehmen ein)

    3 500 Millisekunden?


    Wir müssen den Datenbankaktualisierungsvorgang vor der zweiten Redis-Löschung abschließen. Nehmen wir an, wenn kein dritter Schritt erfolgt, besteht eine hohe Wahrscheinlichkeit, dass die Daten in der Datenbank nach Abschluss der beiden Redis-Löschvorgänge nicht aktualisiert wurden. Wenn zu diesem Zeitpunkt eine Anforderung zum Zugriff auf die Daten vorliegt, liegt das Problem vor Die eingangs erwähnte Frage wird auftauchen.

    4. Warum muss der Cache zweimal gelöscht werden?

    Wenn wir keinen zweiten Löschvorgang durchführen und zu diesem Zeitpunkt eine Anforderung zum Zugriff auf Daten vorliegt, sind die Redis-Daten möglicherweise nicht geändert. Nach der Ausführung des Löschvorgangs ist Redis leer Wenn eine Anfrage eingeht, wird auf die Datenbank zugegriffen. Zu diesem Zeitpunkt handelt es sich bei den Daten in der Datenbank bereits um aktualisierte Daten, wodurch die Datenkonsistenz sichergestellt wird.

    2. Code-Praxis

    1. Einführung von Redis- und SpringBoot-AOP-Abhängigkeiten

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

    /**
     *延时双删
     **/
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Target(ElementType.METHOD)
    public @interface ClearAndReloadCache {
        String name() default "";
    }
    . Schreiben Sie benutzerdefinierte AOP-Anmerkungen und -Aspekte

    3. Anwendung. yml

    @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;//返回业务代码的值
        }
    }

    4, user_db.sql-Skript

    wird zum Erzeugen von Testdaten verwendet

    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

    5, UserController

    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, &#39;张三&#39;);
    INSERT INTO `user_db` VALUES (2, &#39;李四&#39;);
    INSERT INTO `user_db` VALUES (3, &#39;王二&#39;);
    INSERT INTO `user_db` VALUES (4, &#39;麻子&#39;);
    INSERT INTO `user_db` VALUES (5, &#39;王三&#39;);
    INSERT INTO `user_db` VALUES (6, &#39;李三&#39;);

    6, UserService

    /**
     * 用户控制层
     */
    @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);
        }
    }

    3. Testverifizierung

    1, ID=10, neue Daten hinzufügen

    2 In Redis speichern

    5, aktualisieren Sie den Benutzernamen, der der ID 10 entspricht (Datenbank- und Cache-Inkonsistenzschema überprüfen). Ein Thread zum Ausführen des ersten Nach einem Löschvorgang, bevor A die Aktualisierung der Datenbank abschließt, greift ein anderer Thread B auf ID=10 zu und liest die alten Daten.

    Wie SpringBoot AOP Redis die verzögerte Doppellöschfunktion implementiert

    Verwenden Sie den zweiten Löschvorgang und stellen Sie die entsprechende Verzögerungszeit entsprechend dem Geschäftsszenario ein. Nachdem der Cache zweimal erfolgreich gelöscht wurde, ist das Ausgabeergebnis von Redis leer. Was gelesen wird, sind die tatsächlichen Daten der Datenbank, und es besteht keine Inkonsistenz zwischen dem Lesecache und der Datenbank.

    Wie SpringBoot AOP Redis die verzögerte Doppellöschfunktion implementiert

    4. Code Engineering

    Der Kerncode wird im roten Feld angezeigtWie SpringBoot AOP Redis die verzögerte Doppellöschfunktion implementiert

    Das obige ist der detaillierte Inhalt vonWie SpringBoot AOP Redis die verzögerte Doppellöschfunktion implementiert. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Stellungnahme:
    Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen