Bootstrap

电商项目高级篇07-redisson分布式锁

1、引入maven依赖

 <!--引入redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.0</version>
        </dependency>

2、config类

MyRedissonConfig

package com.ljs.gulimall.product.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class MyRedissonConfig {

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
        // 默认连接地址 127.0.0.1:6379
        Config config = new Config();
//可以用"rediss://"来启用 SSL 连接
        config.useSingleServer().setAddress("redis://xxx:6379").setPassword("xxxxx");
        return Redisson.create(config);
    }
}

在这里插入图片描述

3、可重入锁设计

在这里插入图片描述

redisson不存在死锁问题

 @ResponseBody
    @GetMapping("/hello")
    public String hello(){
        // 1、获取一把锁
        RLock lock = redisson.getLock("my-lock");

        // 2、加锁
        lock.lock();
        // 3、执行业务代码
        try{
            System.out.println("加锁成功,执行业务代码。。。"+Thread.currentThread().getId());
            Thread.sleep(30000);
        }catch (Exception exception) {

        }finally {
            // 4、解锁
            System.out.println("释放锁。。。"+Thread.currentThread().getId());
            lock.unlock();
        }
        return "hello";
    }

无论程序是否异常。程序正常执行时看门狗机制会锁自动续期,程序异常时会30秒后释放锁。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

4、读写锁

IndexController

   @Autowired
    private StringRedisTemplate redisTemplate;
  @GetMapping("/write")
    @ResponseBody
    public String writeValue(){
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = lock.writeLock();
        try {
            // 1、改数据加写锁,读数据加读锁
            rLock.lock();
            System.out.println("写锁加锁成功。。。"+Thread.currentThread().getId());
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue",s);
        }catch (Exception ex) {

        }finally {
            rLock.unlock();
            System.out.println("写锁释放。。。"+Thread.currentThread().getId());
        }
        return s;
    }

    @GetMapping("/read")
    @ResponseBody
    public String readValue(){
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = lock.readLock();
        try {
            // 1、改数据加写锁,读数据加读锁
            rLock.lock();
            System.out.println("读锁加锁成功。。。"+Thread.currentThread().getId());
            s= redisTemplate.opsForValue().get("writeValue");
        }catch (Exception ex) {

        }finally {
            rLock.unlock();
            System.out.println("读锁释放。。。"+Thread.currentThread().getId());
        }
        return s;
    }

在这里插入图片描述

当写锁存在、读锁必须等待

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
当写锁释放时。读锁全部都加锁

在这里插入图片描述

5、闭锁

 @GetMapping("/lockDoor")
    @ResponseBody
    /**
     * 5个部门走完才能关大门
     */
    public String lockDoor() throws InterruptedException {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);
        door.await(); // 等待闭锁完成
        return "关大门";
    }

    @GetMapping("/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Integer id){
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown(); //计数减一
        return id + "号部门的人都下班走了";
    }

在这里插入图片描述
/lockDoor方法会一直等待。闭锁数量减为0

在这里插入图片描述

当5个部门的人走光以后,关大门

在这里插入图片描述

6、信号量

  @GetMapping("/park")
    @ResponseBody
    /**
     * 车库停车
     * 3个车位
     */
    public String park () throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        park.acquire();
        return "ok";
    }

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

redis里增加一个键值:车位:1

在这里插入图片描述

调用停车方法
在这里插入图片描述
返回ok

再调用一次发现阻塞等待
在这里插入图片描述

这时候需要释放一个车位这边才能停车、调用/go方法

在这里插入图片描述

在这里插入图片描述

7、缓存一致性问题解决

新增方法与业务结合。改造成redisson分布式锁

CategoryServiceImpl

 /**
     * 分布式锁
     * 缓存和数据库的数据如何保持一致性
     * 1、双写模式
     * 2、失效模式
     *
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatalogDbWithRedissonLock() {
        // 分布式锁
        RLock lock = redisson.getLock("CatalogJson-lock");
        lock.lock();
        System.out.println("获取分布式锁成功...");
        Map<String, List<Catelog2Vo>> dataFromDb;
        try {
            dataFromDb = this.getDataFromDb();
        } finally {
            lock.unlock();
        }
        return dataFromDb;

    }
 @Override
    public Map<String, List<Catelog2Vo>> getCatalog() {
        // 1、获取缓存中的数据
        String catalog = redisTemplate.opsForValue().get("catalog");
        if (StrUtil.isBlank(catalog)) {
            System.out.println("缓存不命中,准备要查询数据库");
            // 2、从数据库中获取数据
            return this.getCatalogDbWithRedissonLock();
        }
       // 将缓存中的数据返回
        System.out.println("缓存命中,直接返回");
        return JSON.parseObject(catalog,Map.class);
    }

解决缓存和数据库不同步问题:
1、双写模式
在这里插入图片描述
双写模式可能会导致脏数据

2、失效模式
在这里插入图片描述
失效模式也可能有脏数据。

###对于经常修改。对数据实时性要求高的数据,最好不要加缓存,缓存只能保证最终一致性,而不是强一致性。应直接读数据库。对于数据实时性要求不高的数据。可以加缓存,如有业务需要可以加读写锁。
如果觉得加锁系统显得臃肿。可以集成阿里巴巴的canal。将数据一致性问题交给canal来做。它相当于mysql从库。我们对mysql数据进行更新时,打开binlog日志。canal将更新日志订阅。然后更新redis值。

在这里插入图片描述

在这里插入图片描述

;