Redis分布式锁

背景:

基于优惠卷抢票下面实现的分布式锁。使用 redission 组件来实现。

1. 什么是分布式锁

前端 nginx 将流量代理到不同的服务上面去,不同的实列服务访问同一个资源。

分布式锁是控制分布式系统同时访问资源的一种方式,在单机或者单进程下面,多线程并发的时候,可以加一个锁比如 synchronized 或者 Reentrantlock 类来控制资源访问。在分布式系统简单加锁就不适用了。前面两个都是在一个 JVM 或者线程有效的,分布式情况下,是跨多个 JVM 的,所以这种锁就会失效。

常见的分布式锁可以用 Redis 去实现。

https://www.cnblogs.com/wangyingshuo/p/14510524.html

2. 分布式锁的特性

分布式锁需要保证以下的特点:

互斥性:只有一个竞争者才能持有锁,这一点要尽可能保证。

对称性:同一个锁,加锁和解锁只能时同一个竞争者,不能把其他竞争者的锁给释放 了,UUID 作为 value,保证自己的锁只能自己释放。

可靠性:比如正在拿锁的服务挂掉了,要有一些容灾的处理。配置超时时间等

3.实现

3.1 通过 Redis setnx 命令

通过 Redis 的 setnx 命令来设置一个分布式锁。

setnx  key value

通过 setnx key value 原理: 如果 key 不存在那么会返回 1,如果 key 存在那么就返回 0.

执行完成逻辑之后之后再 delete key 就可以释放锁了

SETNX lock:order:123 "thread-123-uuid-abc"  # 尝试获取锁
key 就是业务的 ID,Value 是对应的线程 ID+拼接 UUID。
3.2 删除锁

对于 delete 操作:

value 必须不同,存在 A 获取锁的时候,业务执行时间太长了,锁自动过期了,然后 B 获取到了锁,这个时候锁已经被占用了,A 线程结束 了执行,这个时候删除了这把锁,那么其他线程就会进行修改数据。

还要要为 delete 失败进行兜底。如果执行逻辑过程中服务挂了,那么锁一直就会存在,其他线程就只能在外面等待。

使用 lua 脚本来删除锁

3.3 锁需要有过期时间

如果 delete 的服务挂掉了,那么锁一直就得不到释放。需要加一个过期的时间,expire,但是 setnx 和 expire 并不具备原子性,如果 setnx 获取锁之后,服务挂掉了,那么也不行。

redis 提供了 set key value nx ex seconds。 nx 表示具有 setnx 的特定,ex 表示增加了过期时间,最后一个参数就是过期时间的值。

3.4 互斥性保证

客户端 A 还在处理业务当中,时间太长锁过期了。 被 B 使用 del 命令删除了,如果马上有其他客户端来获取锁,就会和 A 一起共享数据了。

给这个锁的 value 设置值得时候, UUID+线程 ID。保证每一个客户端的唯一标识不一样,当删除锁的时候,需要判断检查这个 value 和客户端标识是否一样。 这个也会有问题,删除锁就变成了 读取变量、判断变量、删除变量多个操作,就要保证原子性。这个地方就需要整合 Redis 的 LUA 脚本,整合 LUA 脚本。 为什么不适用线程 ID 呢,不同的 JVM 线程的 ID 可能相同。

3.5 锁续期

有的业务可能因为慢 sql 或者调用第三方执行时间太长了,导致锁已经过去了,这个时候 b 拿到锁,a 执行完成之后再去释放锁,就会出现问题。

可以使用看门狗 watch dog 看门够机制 机制来实现这个问题,新启动一个线程每隔一段时间看一下锁,如果还持有锁,那么就给锁进行延期。

Redission

可以使用 Redission 来实现分布式锁。

1.看门狗机制

2.while 循环不断获取锁

3.redission 可重入

Redission 设置锁,通过配置 redissonClient 拿到 Rlock。

redisson 实现的分布式锁-可重入

底层基于 hash 来实现的,key 是线程 ID,Value 是加锁的次数。

redisson 实现的分布式锁数据一致性

如果某个主机拿到锁还没有释放,数据也没有同步到另一个从节点上面,主节点挂了。

springboot 继承 Redission
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.Redisson;

public class RedissonDistributedLock {

    private RedissonClient redissonClient;

    public RedissonDistributedLock() {
        // 创建 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        this.redissonClient = Redisson.create(config);
    }

    // 获取锁
    public boolean tryLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁,最多等待 10 秒,上锁后 30 秒自动释放
            return lock.tryLock(10, 30, java.util.concurrent.TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    }

    // 释放锁
    public void releaseLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    public static void main(String[] args) {
        RedissonDistributedLock lock = new RedissonDistributedLock();
        String lockKey = "myLock";

        if (lock.tryLock(lockKey)) {
            System.out.println("Lock acquired");
            // 执行任务
            lock.releaseLock(lockKey);
            System.out.println("Lock released");
        } else {
            System.out.println("Unable to acquire lock");
        }
    }
}

总结

面试问题

你提到了 lua,lua 能保证原子性吗?

lua 脚本本身是不能保证原子性的,能保证原子性的是 Redis 的核心逻辑是单线程的。

分布式锁实现的要点是什么?

互斥性、对称性、可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈阿星

您的支持是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值