分布式锁-Redisson简介与整合

一、 Redisson简介

Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格,相较于暴露底层操作的Jedis,Redisson提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务。

二、Redisson与springboot的整合

(1)、pom

        <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.6</version>
        </dependency>

(2)、书写配置
配置路径:https://github.com/redisson/redisson/wiki/2.-Configuration

支持程序配置也支持yml配置,在这里使用的是单机配置

在这里插入图片描述
在这里插入图片描述
配置类:

@Configuration
public class RedissonConfig {
    @Bean(destroyMethod = "shutdown")
    public RedissonClient getRedisson() {
        Config config = new Config();
        // 使用单节点配置
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");
        return Redisson.create(config);
    }
}
测试分布式可重入锁:
@ResponseBody
@GetMapping("/hello")
public String hello() {
    //1、获取同一把锁,只要锁的名字一样,就是同一把锁,
    RLock lock = redisson.getLock("my-lock");
    //2、加锁
    //阻塞式等待
    lock.lock();
    try{
        System.out.println("加锁成功,执行业务"+Thread.currentThread().getId());
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        //3、解锁
        System.out.println(Thread.currentThread().getId()+"释放锁");
        lock.unlock();
    }
    return "hello";
}

问题(1)、两个商品服务10000和10002端口,分别发请求,然后中断10000端口,没有手动释放锁,看看redisson会不会死锁?业务执行时间过长,会不会提前释放锁?
在这里插入图片描述

结论:
(1)、手动没有解锁,也会为自动解锁,有一个自动过期时间,默认为30秒

(2)、在业务未执行完毕,会自动续期,不断获取锁,只要能获取锁就继续执行我们的业务,每隔默认时间的/3s时间,自动蓄满(默认是每隔10s中,自动续期到30s)

在这里插入图片描述
问题(2)、手动添加过期时间时间,执行业务时间过长,看门狗会自动续期吗?
结论:不会续期,超过过期时间,就会有下一个线程进入,例如设置的超时时间为10s,业务的执行时间为30s,当业务执行时间超过10s后,会自动删除redis中的锁,放下一个线程进来继续执行业务逻辑,当第一个线程30s后执行完毕去删锁,发现redis中的锁不是自己的,就会爆出异常。

在这里插入图片描述

测试分布式读写锁
/**
 * 保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁
 * 写锁没释放,读就必须等待
 *
 * @return
 */
@GetMapping("/read")
@ResponseBody
public String readValue() {

    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    String s = "";
    RLock rLock = readWriteLock.readLock();
    try {
        rLock.lock();
        s = stringRedisTemplate.opsForValue().get("writeValue");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        rLock.unlock();
    }
    return s;
}


@GetMapping("/write")
@ResponseBody
public String writeValue() {

    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    String s = "";
    RLock rLock = readWriteLock.writeLock();
    try {
        //1、改数据加写锁,读数据加读锁
        rLock.lock();
        TimeUnit.SECONDS.sleep(10);
        s = UUID.randomUUID().toString();
        stringRedisTemplate.opsForValue().set("writeValue", s);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        rLock.unlock();
    }
    return s;
}

结论:保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁,写锁没释放,读就必须等待

  • 读 + 读 模式 :相当于无锁,并发读,只会在redis中记录好,所有当前的读锁,他们都会同时加锁成功,不需要等待。
  • 写 + 读 模式 :等待写锁释放
  • 写 + 写 模式:阻塞方式
  • 读 + 写 模式:有读锁,写也需要等待
  • 只要有写的存在,都必须等待
分布式信号量测试
/**
 * 信号量可以做分布式限流
 *
 * @return
 * @throws InterruptedException
 */
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {

    RSemaphore park = redisson.getSemaphore("park");
    boolean b = park.acquire();
    if (b) {
        //执行业务
    } else {
        return "error";
    }
    return "ok" + b;

}

@GetMapping("/go")
@ResponseBody
public String go() throws InterruptedException {
    RSemaphore park = redisson.getSemaphore("park");
    park.release();
    return "走了";

}

结论:
park.acquire();获取一个车位,或者一个值,
park.release();释放一个车位,或者一个值。

给redis锁一个初始值默认3,经过多次获取车位后,当redis中的值为0时,再次获取时,会进行阻塞,直到他的地方进行释放,然后才能获取到车位。
可以进行分布式限流,例如某个服务只允许并发10000个请求,请求满了之后,就进行阻塞状态,直到有其他的请求执行完毕后,才停止阻塞,执行业务。

分布式闭锁
@GetMapping("/lockdoor")
@ResponseBody
public String lockDoor() throws InterruptedException {

    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.trySetCount(5);// 设置总数5
    door.await();//等待
    return "放假了";
}

@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable int id){

    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.countDown(); // 计数减一操作
    return id+"号走了";
}

分布式缓存一致性以及解决方案
分布式缓存一致性之双写模式

在这里插入图片描述可能出现的问题:写写问题,更改数据库时更改缓存
当请求1和请求2同时去修改一个数据时,请求1先抢占到资源更改数据库,因为更改数据库和更改缓存不是原子性操作,可能在请求1更改完数据库之后,被请求2抢占到资源,进行更改数据库操作,然后修改缓存,之后请求1再去修改缓存,造成了数据库和缓存中的数据不一致的问题,造成了脏数据问题。

解决方案:
(1)、如果对缓存一致性要求不高的话,可以在增加缓存的时候增加过期时间,出现脏数据之后,缓存过期后,就能重新获取到正确的数据,保证数据的最终一致性。
(2)、如果对缓存一致性要求比较高的话,可以增加读写锁,在更新数据的时候,读数据等待。

分布式缓存一致性之失效模式

在这里插入图片描述
可能出现的问题:更新数据库,删除缓存,等待下一次查询数据时,更新缓存

当请求1和请求2、请求3同时去修改一个数据时,请求1先抢占到资源更改数据库,删除缓存,这时候被请求2抢占到资源更改数据库,请求2还没有更改数据库完成时,请求3去到缓存中读取数据,发现缓存中没有,就去读取数据库,准备更新缓存,在这时请求2更改数据库完毕,删除缓存完毕,最后请求3因为更新了缓存。造成了数据库数据和缓存数据不一致的问题。

解决方案:
(1)、如果对缓存一致性要求不高的话,可以在增加缓存的时候增加过期时间,出现脏数据之后,缓存过期后,就能重新获取到正确的数据,保证数据的最终一致性。
(2)、如果对缓存一致性要求比较高的话,可以增加读写锁,在更新数据的时候,读数据等待。

在这里插入图片描述

完美的解决方案:使用canal中间件,缺点是增加了一个中间件
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值