atguigu3 分布式锁(product/category)

0. 问题:

本地缓存和分布式缓存_价值成长的博客-CSDN博客

读模式的缓存失效问题_价值成长的博客-CSDN博客

写模式的缓存一致性问题_价值成长的博客-CSDN博客

使用分布式锁解决读模式缓存失效(缓存击穿)和写模式缓存一致性问题!!!

公共代码:

CategoryBrandRelationService.java

package com.atguigu.gulimall.product.service;

import com.atguigu.gulimall.product.entity.BrandEntity;
import com.baomidou.mybatisplus.extension.service.IService;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.gulimall.product.entity.CategoryBrandRelationEntity;

import java.util.List;
import java.util.Map;

/**
 * 品牌分类关联
 *
 * @author jude
 * @email [email protected]
 * @date 2022-08-24 13:21:08
 */
public interface CategoryBrandRelationService extends IService {

    PageUtils queryPage(Map params);

    void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);

    void updateBrand(Long brandId, String name);

    void updateCategory(Long catId, String name);

    List getBrandsByCatId(Long catId);
}

CategoryServiceImpl.java:

getCatalogJson()

@Cacheable(value = "category",key = "#root.methodName")
@Override
public Map> getCatalogJson() {
    //给缓存中放json字符串,拿出的json字符串,反序列为能用的对象
    //1、加入缓存逻辑,缓存中存的数据是json字符串
    //JSON跨语言。跨平台兼容。
    String catalogJson = redisTemplate.opsForValue().get("catalogJson");
    if (StringUtils.isEmpty(catalogJson)) {
        System.out.println("缓存不命中...查询数据库...");
        //2、缓存中没有数据,查询数据库
        //Map> catalogJsonFromDb = getCatalogJsonFromDbWithLocalLock();
        Map> catalogJsonFromDb = getCatalogJsonFromDbWithRedisLock();
        // Map> catalogJsonFromDb = getCatalogJsonFromDbWithRedissonLock();
        return catalogJsonFromDb;
    }
    System.out.println("缓存命中...直接返回...");
    //转为指定的对象
    Map> result = JSON.parseObject(catalogJson,new TypeReference>>(){});
    return result;
}

 getDataFromDb()

private Map> getDataFromDb() {
    //得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
    String catalogJson = redisTemplate.opsForValue().get("catalogJson");
    if (!StringUtils.isEmpty(catalogJson)) {
        //缓存不为空直接返回
        Map> result = JSON.parseObject(catalogJson, new TypeReference>>() {
        });

        return result;
    }

    System.out.println("查询了数据库");

    /**
     * 将数据库的多次查询变为一次
     */
    List selectList = this.baseMapper.selectList(null);

    //1、查出所有分类
    //1、1)查出所有一级分类
    List level1Categorys = getParent_cid(selectList, 0L);

    //封装数据
    Map> parentCid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
        //1、每一个的一级分类,查到这个一级分类的二级分类
        List categoryEntities = getParent_cid(selectList, v.getCatId());

        //2、封装上面的结果
        List catelog2Vos = null;
        if (categoryEntities != null) {
            catelog2Vos = categoryEntities.stream().map(l2 -> {
                Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName().toString());

                //1、找当前二级分类的三级分类封装成vo
                List level3Catelog = getParent_cid(selectList, l2.getCatId());

                if (level3Catelog != null) {
                    List category3Vos = level3Catelog.stream().map(l3 -> {
                        //2、封装成指定格式
                        Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());

                        return category3Vo;
                    }).collect(Collectors.toList());
                    catelog2Vo.setCatalog3List(category3Vos);
                }

                return catelog2Vo;
            }).collect(Collectors.toList());
        }

        return catelog2Vos;
    }));

    //3、将查到的数据放入缓存,将对象转为json
    String valueJson = JSON.toJSONString(parentCid);
    // 加入过期时间(随机值),防止缓存雪崩
    redisTemplate.opsForValue().set("catalogJson", valueJson, 1, TimeUnit.DAYS);

    return parentCid;
}

1. 本地锁和分布式锁

atguigu3 分布式锁(product/category)_第1张图片

 atguigu3 分布式锁(product/category)_第2张图片

atguigu3 分布式锁(product/category)_第3张图片

atguigu3 分布式锁(product/category)_第4张图片

本地锁代码实现(读模式):

CategoryServiceImpl.java:

getCatalogJsonFromDbWithLocalLock()

public Map> getCatalogJsonFromDbWithLocalLock() {
    //1、synchronized (this):SpringBoot所有的组件在容器中都是单例的。
    //TODO 本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有,必须使用分布式锁
    synchronized (this) {
        //得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
        return getDataFromDb();
    }
}

2. redis分布式锁

atguigu3 分布式锁(product/category)_第5张图片

atguigu3 分布式锁(product/category)_第6张图片

atguigu3 分布式锁(product/category)_第7张图片

atguigu3 分布式锁(product/category)_第8张图片

atguigu3 分布式锁(product/category)_第9张图片redis分布式锁代码实现(读模式):只实现了类似ReentrantLock

CategoryServiceImpl.java:

getCatalogJsonFromDbWithRedisLock()

public Map> getCatalogJsonFromDbWithRedisLock() {
    // 加锁保证原子性 set resource_name anystring NX EX max-lock-time
    // 解锁保证原子性 lua脚本
    //1、占分布式锁。去redis占坑 设置过期时间必须和加锁是同步的,保证原子性(避免死锁) setIfAbsent==setNX/setNXEX
    // uuid保证加锁和删锁是同一个锁
    String uuid = UUID.randomUUID().toString();
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
    if (lock) {
        System.out.println("获取分布式锁成功...");
        Map> dataFromDb = null;
        try {
            //加锁成功...执行业务
            dataFromDb = getDataFromDb();
        } finally {
            // lua脚本解锁,可以保证操作的原子性
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            //删除锁
            redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList("lock"), uuid);
        }
        return dataFromDb;
    } else {
        System.out.println("获取分布式锁失败...等待重试...");
        //加锁失败,重试机制,休眠一百毫秒
        try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        return getCatalogJsonFromDbWithRedisLock();     //自旋的方式
    }
}

核心代码:

///加锁
String token = UUID.randomUUID().toString();
String lock = jedis.set(key, token, "NX", "EX",20);

3. redisson分布式锁:基于the Redlock algorithm

1. Overview · redisson/redisson Wiki · GitHub

Redisson功能:分布式对象,分布式集合,分布式锁

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSetSetMultimapSortedSetMapListQueueBlockingQueueDequeBlockingDequeSemaphoreLockAtomicLongCountDownLatchPublish / SubscribeBloom filterRemote serviceSpring cacheExecutor serviceLive Object serviceScheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

一句话:将分布式对象存放在redis里。

特点:1. 底层时lua脚本,保证了锁的原子性。 2. 看门狗机制(锁过期自动续期),解决死锁问题。

3.1 eg: IndexController.java

package com.atguigu.gulimall.product.web;

import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Controller
public class IndexController {

    @Autowired
    CategoryService categoryService;

    @Autowired
    RedissonClient redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @ResponseBody
    @GetMapping(value = "/hello")
    public String hello() {

        //1、获取一把锁,只要锁的名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");

        //2、加锁
        lock.lock();      //阻塞式等待。默认加的锁都是30s
        //1)、锁的自动续期,如果业务超长,运行期间自动锁上新的30s。不用担心业务时间长,锁自动过期被删掉
        //2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题
        // lock.lock(10,TimeUnit.SECONDS);   //最佳实战:10秒钟自动解锁,自动解锁时间一定要大于业务执行时间
        //问题:在锁时间到了以后,不会自动续期
        //1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是 我们制定的时间
        //2、如果我们指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000 【看门狗默认时间】
        //只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动的再次续期,续成30秒
        // internalLockLeaseTime 【看门狗时间】 / 3, 10s
        try {
            System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
            try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            //3、解锁  假设解锁代码没有运行,Redisson会不会出现死锁
            System.out.println("释放锁..." + Thread.currentThread().getId());
            lock.unlock();
        }

        return "hello";
    }

    /**
     * 保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁
     * 写锁没释放读锁必须等待
     * 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
     * 写 + 读 :必须等待写锁释放
     * 写 + 写 :阻塞方式
     * 读 + 写 :有读锁。写也需要等待
     * 只要有写的存都必须等待
     * @return
     */
    @GetMapping(value = "/write")
    @ResponseBody
    public String writeValue() {
        String s = "";
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
        RLock rLock = readWriteLock.writeLock();
        try {
            //1、改数据加写锁,读数据加读锁
            rLock.lock();
            s = UUID.randomUUID().toString();
            ValueOperations ops = stringRedisTemplate.opsForValue();
            ops.set("writeValue",s);
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }

        return s;
    }

    @GetMapping(value = "/read")
    @ResponseBody
    public String readValue() {
        String s = "";
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
        //加读锁
        RLock rLock = readWriteLock.readLock();
        try {
            rLock.lock();
            ValueOperations ops = stringRedisTemplate.opsForValue();
            s = ops.get("writeValue");
            try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }

        return s;
    }

    /**
     * 车库停车
     * 3车位
     * 信号量也可以做分布式限流
     */
    @GetMapping(value = "/park")
    @ResponseBody
    public String park() throws InterruptedException {

        RSemaphore park = redisson.getSemaphore("park");
        park.acquire();     //获取一个信号、获取一个值,占一个车位
        boolean flag = park.tryAcquire();

        if (flag) {
            //执行业务
        } else {
            return "error";
        }

        return "ok=>" + flag;
    }

    @GetMapping(value = "/go")
    @ResponseBody
    public String go() {
        RSemaphore park = redisson.getSemaphore("park");
        park.release();     //释放一个车位
        return "ok";
    }


    /**
     * 放假、锁门
     * 1班没人了
     * 5个班,全部走完,我们才可以锁大门
     * 分布式闭锁
     */

    @GetMapping(value = "/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {

        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();       //等待闭锁完成

        return "放假了...";
    }

    @GetMapping(value = "/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Long id) {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown();       //计数-1

        return id + "班的人都走了...";
    }

    @GetMapping({"/","index.html"})
    public String indexPage(Model model){
        List categoryEntities = categoryService.getLevel1Category();
        model.addAttribute("categories", categoryEntities);
        return "index";
    }

    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map> getCatalog(){
        Map> catalogJson = categoryService.getCatalogJson();
        return catalogJson;
    }
}

3.2 redisson分布式锁代码实现(读模式)​​​​​​​

pom.xml:


    org.redisson
    redisson
    3.12.0

MyRedissonConfig:

@Configuration
public class MyRedissonConfig {

    /**
     * 所有对Redisson的使用都是通过RedissonClient
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        //单节点模式

        //1、创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.56.10:6371");

        //2、根据Config创建出RedissonClient实例
        //Redis url should start with redis:// or rediss://
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

CategoryServiceImpl:

getCatalogJsonFromDbWithRedissonLock()

@Autowired
private RedissonClient redissonClient;

public Map> getCatalogJsonFromDbWithRedissonLock() {
    //1、占分布式锁。去redis占坑
    //(锁的粒度,越细越快:具体缓存的是某个数据,11号商品) product-11-lock
    //RLock catalogJsonLock = redissonClient.getLock("catalogJson-lock");
    //创建读锁
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");
    RLock rLock = readWriteLock.readLock();
    Map> dataFromDb = null;
    try {
        rLock.lock();
        //加锁成功...执行业务
        dataFromDb = getDataFromDb();
    } finally {
        rLock.unlock();
    }
    return dataFromDb;
}

3.3 redisson分布式锁代码实现(写模式,整合 spring cache:简化缓存开发)

​​​​​​​Spring Cache_价值成长的博客-CSDN博客

pom.xml


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


    org.springframework.boot
    spring-boot-starter-cache

application.properties

spring.cache.type=redis
spring.cache.redis.time-to-live=3600000
# 缓存前缀,如果没有就默认使用缓存的名字作为前缀
#spring.cache.redis.key-prefix=CACHE_
# 是否缓存空置:解决缓存穿透问题
spring.cache.redis.use-key-prefix=true
spring.cache.redis.cache-null-values=true
GulimallProductApplication.java 开启缓存
package com.atguigu.gulimall.product;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * 1、整合MyBatis-Plus
 *      1)、导入依赖
 *      
 *             com.baomidou
 *             mybatis-plus-boot-starter
 *             3.2.0
 *      
 *      2)、配置
 *          1、配置数据源;
 *              1)、导入数据库的驱动。https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-versions.html
 *              2)、在application.yml配置数据源相关信息
 *          2、配置MyBatis-Plus;
 *              1)、使用@MapperScan
 *              2)、告诉MyBatis-Plus,sql映射文件位置
 *
 * 2、逻辑删除
 *  1)、配置全局的逻辑删除规则(省略)
 *  2)、配置逻辑删除的组件Bean(省略)
 *  3)、给Bean加上逻辑删除注解@TableLogic
 *
 * 3、JSR303
 *   1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
 *   2)、开启校验功能@Valid
 *      效果:校验错误以后会有默认的响应;
 *   3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
 *   4)、分组校验(多场景的复杂校验)
 *         1)、	@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
 *          给校验注解标注什么情况需要进行校验
 *         2)、@Validated({AddGroup.class})
 *         3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
 *
 *   5)、自定义校验
 *      1)、编写一个自定义的校验注解
 *      2)、编写一个自定义的校验器 ConstraintValidator
 *      3)、关联自定义的校验器和自定义的校验注解
         *      @Documented
         * @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型的校验】 })
         * @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
         * @Retention(RUNTIME)
         * public @interface ListValue {
 *
 * 4、统一的异常处理
 * @ControllerAdvice
 *  1)、编写异常处理类,使用@ControllerAdvice。
 *  2)、使用@ExceptionHandler标注方法可以处理的异常。
 *
 *
 * 5、模板引擎
 *  1)、thymeleaf-starter:关闭缓存
 *  2)、静态资源都放在static文件夹下就可以按照路径直接访问
 *  3)、页面放在templates下,直接访问
 *      SpringBoot,访问项目的时候,默认会找index
 *  4)、页面修改不重启服务器实时更新
 *      1)、引入dev-tools
 *      2)、修改完页面 controller shift f9重新自动编译下页面,代码配置,推荐重启
 *
 * 6、整合redis
 *  1)、引入data-redis-starter
 *  2)、简单配置redis的host等信息
 *  3)、使用SpringBoot自动配置好的StringRedisTemplate来操作redis
 *      redis-》Map;存放数据key,数据值value
 *
 * 7、整合redisson作为分布式锁等功能框架
 *      1)、引入依赖
 *              
 *             org.redisson
 *             redisson
 *             3.12.0
 *         
 *      2)、配置redisson
 *              MyRedissonConfig给容器中配置一个RedissonClient实例即可
 *      3)、使用
 *          参照文档做。
 *
 * 8、整合SpringCache简化缓存开发
 *      1)、引入依赖
 *          spring-boot-starter-cache、spring-boot-starter-data-redis
 *      2)、写配置
 *          (1)、自动配置了哪些
 *              CacheAuroConfiguration会导入 RedisCacheConfiguration;
 *              自动配好了缓存管理器RedisCacheManager
 *          (2)、配置使用redis作为缓存
 *              spring.cache.type=redis
 *      3)、测试使用缓存
 *          @Cacheable: Triggers cache population.:触发将数据保存到缓存的操作
 *          @CacheEvict: Triggers cache eviction.:触发将数据从缓存删除的操作
 *          @CachePut: Updates the cache without interfering with the method execution.:不影响方法执行更新缓存
 *          @Caching: Regroups multiple cache operations to be applied on a method.:组合以上多个操作
 *          @CacheConfig: Shares some common cache-related settings at class-level.:在类级别共享缓存的相同配置
 *          1)、开启缓存功能 @EnableCaching
 *          2)、只需要使用注解就能完成缓存操作
 *
 *      4)、原理:
 *          CacheAutoConfiguration ->  RedisCacheConfiguration ->
 *          自动配置了RedisCacheManager->初始化所有的缓存->每个缓存决定使用什么配置
 *          ->如果redisCacheConfiguration有就用已有的,没有就用默认配置
 *          ->想改缓存的配置,只需要给容器中放一个RedisCacheConfiguration即可
 *          ->就会应用到当前RedisCacheManager管理的所有缓存分区中
 *
 */
//@EnableCaching
@EnableRedisHttpSession
@EnableFeignClients(basePackages = "com.atguigu.gulimall.product.feign")
@EnableDiscoveryClient
@MapperScan("com.atguigu.gulimall.product.dao")
@SpringBootApplication
public class GulimallProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallProductApplication.class, args);
    }


}

MyCacheConfig.java

package com.atguigu.gulimall.product.config;

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {

    // @Autowired
    // public CacheProperties cacheProperties;

    /**
     * 配置文件的配置没有用上
     * @return
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // config = config.entryTtl();
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //将配置文件中所有的配置都生效
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }

        return config;
    }

}

CategoryServiceImpl.java:

updateCascade()

    /*
     * 每一个需要缓存的数据我们都来指定要放到那个名字的缓存。【缓存的分区(按照业务类型分)】
     * 代表当前方法的结果需要缓存,如果缓存中有,方法都不用调用,如果缓存中没有,会调用方法。最后将方法的结果放入缓存
     * 默认行为
     *      如果缓存中有,方法不再调用
     *      key是默认生成的:缓存的名字::SimpleKey::[](自动生成key值)
     *      缓存的value值,默认使用jdk序列化机制,将序列化的数据存到redis中
     *      默认时间是 -1:
     *
     *   自定义操作:key的生成
     *      指定生成缓存的key:key属性指定,接收一个Spel
     *      指定缓存的数据的存活时间:配置文档中修改存活时间
     *      将数据保存为json格式
     *
     *
     * 4、Spring-Cache的不足之处:
     *  1)、读模式
     *      缓存穿透:查询一个null数据。解决方案:缓存空数据
     *      缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题
     *      缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间
     *  2)、写模式:(缓存与数据库一致)
     *      1)、读写加锁。
     *      2)、引入Canal,感知到MySQL的更新去更新Redis
     *      3)、读多写多,直接去数据库查询就行
     *
     *  总结:
     *      常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):写模式(只要缓存的数据有过期时间就足够了)
     *      特殊数据:特殊设计
     *
     *  原理:
     *      CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache负责缓存的读写
     * @return
     */


/**
 * 级联更新所有关联的数据
 *
 * @CacheEvict:失效模式
 * @CachePut:双写模式,需要有返回值
 * 1、同时进行多种缓存操作:@Caching
 * 2、指定删除某个分区下的所有数据 @CacheEvict(value = "category",allEntries = true)
 * 3、存储同一类型的数据,都可以指定为同一分区
 * @param category
 */
// 同时设置多个缓存操作
// @Caching(evict = {
//         @CacheEvict(value = "category",key = "'getLevel1Categorys'"),
//         @CacheEvict(value = "category",key = "'getCatalogJson'")
// })
@CacheEvict(value = "category",allEntries = true)       //删除某个分区下的所有数据
@Transactional(rollbackFor = Exception.class)
@Override
public void updateCascade(CategoryEntity category) {

    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");
    //创建写锁
    RLock rLock = readWriteLock.writeLock();

    try {
        rLock.lock();
        this.baseMapper.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        rLock.unlock();
    }
    //同时修改缓存中的数据
    //删除缓存,等待下一次主动查询进行更新
}

CategoryBrandRelationDao.java

updateCategory()

@Mapper
public interface CategoryBrandRelationDao extends BaseMapper {

    void updateCategory(@Param("catId") Long catId, @Param("name") String name);
}

CategoryBrandRelationDao.xml






	
    
        
        
        
        
        
    
    
        update pms_category_brand_relation set catelog_name=#{name} where catelog_id=#{catId}
    
    

你可能感兴趣的:(java,java)