Redis——分布式锁

基本原理

synchronized是利用JVM内部的锁监视器控制线程,但是只能在一个JVM中生效。如果有多个JVM的时候,就会有多个线程获取到锁,就无法实现多JVM进程之间的互斥了。

因此不能使用JVM内部的锁监视器了,必须使用JVM外部的锁监视器,就能保证只有一个线程获取到锁,就能实现多进程之间的互斥了。

分布式锁是什么

是满足分布式系统或集群模式下多进程可见且互斥的锁。分布式锁可以是悲观锁或乐观锁,具体取决于实现方式。悲观锁是通过独占资源的方式实现的,悲观锁的具体实现如Redis分布式锁、MySQL行锁和表锁。

分布式锁的基本特性

1、多进程可见。

2、互斥(只能被一个线程拿到)。

3、高可用(大多数情况下线程来获取锁都是成功的)。

4、高并发(加锁之后,线程变成串行执行了,性能会下降,所以获取锁的动作要很快)。

5、安全性(避免死锁)。

不同实现方式对比

MySQL支持主从复制;Redis支持主从复制、集群;

基于Redis的分布式锁

基于setnx ex实现

存在许多缺点:

1、不可重入:同一线程无法多次获取同一把锁。

2、不可重试:获取锁没有重试机制。

3、超时释放:锁超时时间长短存在矛盾。

4、主从同步延迟(极端情况):获取锁是SET操作(写操作),主节点写入后未同步给从结点,就可能出现多个线程拿到锁的情况。

Redisson框架实现

Redisson是在Redis的基础上实现的分布式工具的集合。提供了分布式系统下各种各样的工具,包括分布式锁。

推荐自己配置Redisson,不要使用Redisson与SpringBoot的整合方式。

Redisson的使用

1、引入redisson依赖

2、配置Redis(配置文件或配置类)

@Configuration
public class RedisConfig {
    @Bean
    public RedissonClient redissonClient() {
        //配置类
        Config config = new Config();
        //添加redis地址,也可以使用config.useclusterServers()添加集群地址
        config.useSingleServer().setAddress
            ("redis://192.168.150.101:6379").setPassword("123321");
        //创建客户端
        return Redisson.create(config);
    }
}

3、使用Redisson的分布式锁

@Resource
private RedissonClient redissonClient;

@Test  
void testRedisson() throws InterruptedException {
    //1、创建锁对象(可重入),指定锁的名称
    RLock lock = redissonclient.getLock("lock:order:" + id);
    //2、尝试获取锁,参数为:
    //重试获取锁的最大等待时间(期间会重试获取锁),1秒结束,返回false
    //锁超时释放时间(避免宕机造成死锁)
    //时间单位
    boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
    //判断尝试获取锁是否成功
    if(!isLock) {
        //获取锁失败
        return Result.fail("不允许重复下单");
    }
    try {
        System.out.println("执行业务");
    } finally {
        //3、释放锁
        lock.unlock();
    }
}

并发测试

可以使用Fiddler,Jmeter等软件发送请求。

Redisson原理

Redisson可重入锁原理

解决setnx ex锁的不可重入缺点。

在一个线程里连续多次获取锁,这就是锁的重入。所以锁的数据结构不能使用String(简单的key-value),需要使用能在一个key中存储两个数据的数据结构。Redisson使用Hash存储锁,key记录锁名,field记录线程标识,value记录锁的重入次数(与JDK中ReentrantLock原理相同)。

释放锁的时候将重入次数-1,直至所有的业务都执行完,最外层的方法结束释放锁时才能删除锁。因为尝试获取锁和释放锁是成对出现的,所以当value为0时就能判断出最外层的方法已经释放锁,就可以把锁删除。(代码需要lua脚本实现)

Redisson的锁重试和WatchDog机制

解决setnx ex锁的不可重试和超时释放时间长短矛盾的缺点。

/**
 * waitTime 等待时间
 * leaseTime 超时释放时间
 * unit 时间单位
 */
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit);

重试机制

基于信号量和PubSub发布订阅(等待、唤醒,不占用CPU)

线程获取锁失败之后,进行等待,等待释放锁的消息(PubSub机制),得到消息后,去重新尝试获取锁。

线程获取锁成功,在释放锁的时候会发布消息。

超时释放机制

基于WatchDog机制,延续锁的剩余时间。

线程获取锁成功之后,开启一个定时任务,该任务每隔一段时间重置锁的超时时间(EXPIRE),确保锁在非服务宕机的情况下不会因为自动超时而释放。

Redisson的主从同步multiLock联锁原理

解决setnx ex锁的主从同步延迟的缺点。

主从节点中的所有节点都变成独立的Redis结点,都可以读写,此时获取锁的方式改变为依次向所有独立的Redis节点获取锁,即使一个节点宕机,仍然还有其他节点存活,节点越多可用性越高。

也可以给上述Redis节点建立主从关系,进一步提高可用性。一个主节点宕机,且未完成主从同步的情况下,其他线程也是不能获取到锁的。

该方案称为multiLock(联锁)。

使用

为每个节点(Redisson客户端)都新建一个配置。

	@Bean
    public RedissonClient redissonClient2() {
        //配置
        Config config = new Config();
        config.useSingleServer().setAddress
            ("redis://192.168.150.101:6380");
        //创建客户端
        return Redisson.create(config);
    }

	@Bean
    public RedissonClient redissonClient3() {
        //配置
        Config config = new Config();
        config.useSingleServer().setAddress
            ("redis://192.168.150.101:6381");
        //创建客户端
        return Redisson.create(config);
    }
//在需要使用的地方注入多个RedissonClient
@Resource
private RedissonClient redissonClient;
@Resource
private RedissonClient redissonClient2;
@Resource
private RedissonClient redissonClient3;

private RLock lock;

@BeforeEach
void setUp() {
    RLock lock1 = redissonclient.getLock("order");
    RLock lock2 = redissonclient2.getLock("order");
    RLock lock3 = redissonclient3.getLock("order");
    //创建联锁multiLock
    lock = redissonclient.getMultiLock(lockl, lock2, lock3);
}

void func() {
    boolean isLock = lock.tryLock();
}

总结

三类Redis分布式锁:

1、不可重入的Redis分布式锁

2、可重入的Redis分布式锁

3、Redisson的multiLock联锁

posted @   上瘾了  阅读(269)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 易语言 —— 开山篇
· 实操Deepseek接入个人知识库
· Trae初体验
点击右上角即可分享
微信分享提示