Rumah  >  Artikel  >  pangkalan data  >  Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

PHPz
PHPzke hadapan
2023-05-29 08:53:082015semak imbas

    1. Senario perniagaan

    Dalam kes konkurensi berbilang benang, dengan mengandaikan terdapat dua permintaan pengubahsuaian pangkalan data, untuk memastikan ketekalan data antara pangkalan data dan redis,
    Dalam pelaksanaan permintaan pengubahsuaian, anda perlu melata mengubah suai data dalam Redis selepas mengubah suai pangkalan data.
    Permintaan 1: A mengubah suai data pangkalan data B mengubah suai data Redis
    Permintaan 2: C mengubah suai data pangkalan data D mengubah suai data Redis
    Dalam situasi serentak, akan ada A —> ;> D — > Situasi B
    (Pastikan anda memahami bahawa susunan pelaksanaan berbilang set operasi atom secara serentak oleh benang mungkin bertindih)

    1

    A Ubah suai pangkalan data Data akhirnya disimpan ke Redis, dan C juga mengubah suai data pangkalan data selepas A.

    Pada masa ini, terdapat ketidakselarasan antara data dalam Redis dan data dalam pangkalan data Dalam proses pertanyaan seterusnya, Redis akan disemak terlebih dahulu untuk masa yang lama, mengakibatkan situasi yang serius di mana pertanyaan itu. data bukanlah data sebenar dalam pangkalan data.

    2. Penyelesaian

    Apabila menggunakan Redis, anda perlu mengekalkan konsistensi Redis dan data pangkalan data Salah satu penyelesaian yang paling popular ialah strategi pemadaman berganda tertunda.

    Nota: Anda mesti tahu bahawa jadual data yang kerap diubah suai tidak sesuai untuk menggunakan Redis, kerana hasil daripada strategi pemadaman berganda adalah untuk memadamkan data yang disimpan dalam Redis, dan pertanyaan seterusnya akan menanyakan pangkalan data. Oleh itu, Redis menggunakan cache data yang membaca jauh lebih banyak daripada perubahan.
    Langkah pelaksanaan penyelesaian pemadaman berganda tertunda

    1> Padamkan cache

    2> Kemas kini pangkalan data
    3> 🎜>4> Padamkan cache

    3. Mengapakah terdapat kelewatan 500 milisaat?

    Kami perlu melengkapkan operasi kemas kini pangkalan data sebelum pemadaman Redis kedua. Bayangkan jika tiada langkah ketiga, terdapat kebarangkalian tinggi bahawa selepas dua operasi pemadaman Redis selesai, data dalam pangkalan data belum dikemas kini, jika ada permintaan untuk mengakses data, masalahnya kami sebutkan di awal akan muncul soalan itu.

    4. Mengapa anda perlu memadam cache dua kali?

    Jika kami tidak mempunyai operasi pemadaman kedua dan terdapat permintaan untuk mengakses data pada masa ini, ia mungkin data Redis yang belum diubah suai sebelum ini Selepas operasi pemadaman dilaksanakan, Redis akan kosong. Apabila permintaan masuk, ia akan Pangkalan data akan diakses Pada masa ini, data dalam pangkalan data adalah data yang dikemas kini, memastikan konsistensi data.

    2. Amalan kod

    1 Perkenalkan kebergantungan Redis dan SpringBoot

    <!-- 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. Tulis anotasi dan aspek aop tersuai

    ClearAndReloadCache delay double Delete annotation

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

    ClearAndReloadCacheAspect menangguhkan pemadaman berganda bagi aspek

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

    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, skrip user_db.sql

    untuk Data ujian pengeluaran >
    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;);

    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,"操作数据库失败");
        }
    
        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, "操作数据库失败");
        }
    
        public Result update(User user){
            int i = userMapper.updateById(user);
            if(i > 0)
                return Result.success(i);
            return Result.fail(888,"操作数据库失败");
        }
    }

    3 Pengesahan ujian

    1, ID=10, tambah data baharu

    2 Apabila menanyakan pangkalan data untuk kali pertama, Redis akan menyimpan hasil pertanyaan

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

    3

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

    4 ID pangkalan data akses pertama ialah 10, dan simpan hasilnya dalam Redis

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

    5 Untuk nama pengguna yang sepadan dengan 10 (sahkan pangkalan data dan skim ketidakkonsistenan cache)

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

    Skim pengesahan ketidakkonsistenan pangkalan data dan cache:

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertundaLetakkan titik putus dan simulasikan A thread Selepas pemadaman pertama dilakukan, sebelum A selesai mengemas kini pangkalan data, satu lagi thread B mengakses ID=10 dan membaca data lama.

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

    Gunakan pemadaman kedua, selepas menetapkan masa tunda yang sesuai mengikut senario perniagaan, selepas pemadaman cache berjaya dua kali, The output Redis akan kosong. Apa yang dibaca adalah data sebenar pangkalan data, dan tidak akan ada ketidakkonsistenan antara cache baca dan pangkalan data.

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda

    4 Kejuruteraan Kod

    Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertundaKod teras ditunjukkan dalam kotak merah

    Atas ialah kandungan terperinci Cara SpringBoot AOP Redis melaksanakan fungsi pemadaman berganda tertunda. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam