Redis分布式锁

本文详细介绍了分布式环境下的锁机制,包括JVM内置锁、数据库乐观锁和悲观锁、Zookeeper实现、Redis的setnx和过期机制,以及Redission提供的高级锁定功能,如公平锁、联合锁和读写锁,以及它们的优缺点和应用场景。
摘要由CSDN通过智能技术生成

1.常见的分布式锁实现方案

我们常用的synchronized,lock等都是jvm层面的锁,在分布式环境中是无法实现加锁的;常用的分布式锁实现方案有几种:

数据库乐观锁:在表中增加一个version字段,每次更新数据的时候,先获取当前的version,更新的时候对比数据库中的version跟获取的version是否相同;如果不同,则更新失败;算是一种cas操作,每次更新数据的时候加上verson判断;

数据库悲观锁:在更新数据的时候使用select for update;select 的记录行会被锁中,直到事务执行完成才会释放,所以需要执行在事务中;

zookeeper实现分布式锁,主要是使用zk的临时节点的有序性;首先每个线程会在某个目录下创建一个顺序性节点,然后获取这个目录下的所有节点,判断当前的节点,是否是最小节点,如果是,获取到锁;如果是次小节点,则监听比自己小的节点变化,之后重新尝试比较判断自己是否是最小的,是则获取到锁;zk使用curator来操作;

2.redis的setnx命令实现分布式锁

setnx命令,其实如果某个key不存在的话,设置某个key的value;如果存在,则返回失败;

127.0.0.1:6379> SETNX test 'try'
(integer) 1
127.0.0.1:6379> get test
"try"
127.0.0.1:6379> SETNX test 'tryAgain'
(integer) 0
127.0.0.1:6379> get test
"try"

可以配合ex或者px设置key的过期时间

#给lock设置了过期时间为60000毫秒(也可以用ex 6000,单位就变成了秒),当用NX再次赋值,则返回nil,不能重入操作
127.0.0.1:6379> set lock true NX px 60000
OK
127.0.0.1:6379> set lock true NX px 6000
(nil)
127.0.0.1:6379> get lock
"true"
127.0.0.1:6379> ttl lock
(integer) 43
#时间过期后再次get,返回nil,表明key 为 lock的锁已经释放
127.0.0.1:6379> get lock
(nil)

存在问题以及解决方案:

1>不设置过期时间,会导致如果应用崩了,那么这个锁永远无法释放;

2>如果设置了过期时间,那么到了过期时间,业务操作A还没有结束,会导致其他的线程B获取到锁;然后A操作完了之后,直接释放锁,这个时候,等于是把B的锁释放了;解决办法,在释放的时候,判断key对应的value是不是自己的就可以了,如果不是自己的,就不处理;

另外还有几个问题

不支持重入,如果同个线程,再次获取锁,会失败;

不支持续约,就上面提到的两个问题,锁时间太短会导致业务没操作完,锁释放了;锁时间太长,如果宕机会导致其他的线程再也无法获取锁;

不具备阻塞能力,如果没有获取到锁,只能业务上进行cas操作轮训;这种操作应该放在锁上进行操作,而不是放在业务逻辑上;

而Redission则满足上面的几点;

3.Redission实现分布式锁

Redisson使用hash做数据结构,key是业务指定锁名称,field是线程id,value是加锁的次数;

具体代码

依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.10.6</version>
</dependency>

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;
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        // 也可以将 redis 配置信息保存到配置文件
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}

使用方式

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class LockController {
    @Autowired
    private RedissonClient redissonClient;
    @GetMapping("/lock")
    public String lockResource() throws InterruptedException {
        String lockKey = "myLock";
        // 获取 RLock 对象
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁(尝试加锁)(锁超时时间是 30 秒)
            boolean isLocked = lock.tryLock(30, TimeUnit.SECONDS);
            if (isLocked) {
                // 成功获取到锁
                try {
                    // 模拟业务处理
                    TimeUnit.SECONDS.sleep(5);
                    return "成功获取锁,并执行业务代码";
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            } else {
                // 获取锁失败
                return "获取锁失败";
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "获取锁成功";
    }
}

默认创建的是非公平锁,可以使用下面代码创建公平锁;

RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();

还可以创建联合锁

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

还可以创建读写锁

redisson.getReadWriteLock("anyRWLock");
RLock lock = rwlock.readLock();
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
// 或
RLock lock = rwlock.writeLock();
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值