Redis_23_Redis实现分布式锁

本文围绕Redis分布式锁展开,介绍了其原理,需满足加有效时间锁和高性能解锁。阐述了不使用lua脚本(setnx + 超时时间、setnx + del)和使用lua脚本(setnx + 超时时间、setnx + del)实现分布式锁的方法,指出lua脚本可解决分步操作的原子性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

如果服务端程序分布式部署,有两个副本,类似kubernetes的一个Deployment下面两个Pod,有一个task任务,只需要一个Pod去执行就好了。此时,开启一个redis,这个redis不用存储数据,就用来给两个Pod setnx 争抢。

方式1:setnx(“lock”, “1”) + expire(“lock”, 30) 实现,达到超时时间自动解锁,进程/线程/副本重新竞争;

方式2:setnx(“lock”, “1”) + del(“lock”) 实现,执行完逻辑之后手动解锁,进程/线程/副本重新竞争。

相同点:加锁方式相同,都是利用了 setnx 命令只能执行一次的原理,从而创造出一个互斥的条件。

本文源码地址

二、Redis分布式锁原理

Redis分布式锁
思考:分布锁满足两个条件,一个是加有效时间的锁,一个是高性能解锁
注意1:解锁流程不能遗漏,否则导致任务执行一次就永不过期
注意2:将加锁代码和任务逻辑放在try,catch代码块,将解锁流程放在finally(保证解锁逻辑一定执行到)

采用redis命令setnx(set if not exist)、setex(set expire value)实现,如下图:

在这里插入图片描述

三、不使用lua脚本

3.1 setnx + 超时时间

setnx + 超时时间,代码执行如下:

在这里插入图片描述

单独提取到一个类 Distribution1 里面,如下:

public class Distribution1 {
    public static void main(String[] args) throws Exception{
        for (int i = 0; i < 2; i++) {
            new Thread() {
                public void run() {
                    try {
                        long exit = JedisUtil.getJedisUtil().setnx("lock", "1");
                        if (exit == 0) {
                            System.out.println("被锁住了,不能执行");
                        } else {
                            //为什么要给锁添加一个过期时间(这里设置过期时间30秒)?
                            JedisUtil.getJedisUtil().expire("lock", 30);
                            System.out.println("处理业务中");
                            Thread.sleep(3000);  // 模拟业务处理3秒钟
                        }
                    } catch (Exception ex) {

                    }
                }

                ;
            }.start();
            Thread.sleep(1000);
        }
    }
}

在这里插入图片描述

3.2 setnx + del

public class Distribution2 {
    public static void main(String[] args) throws Exception{
        for (int i = 0; i < 2; i++) {
            new Thread() {
                public void run() {
                    try {
                        long exit = JedisUtil.getJedisUtil().setnx("lock2", "1");
                        if (exit == 0) {
                            System.out.println("被锁住了,不能执行");
                        } else {
                            System.out.println("处理业务中");
                            Thread.sleep(3000);  // 模拟业务处理3秒钟
                            // 处理完业务逻辑删除锁
                            JedisUtil.getJedisUtil().del("lock2");
                        }

                    } catch (Exception ex) {

                    }
                };
            }.start();
            Thread.sleep(4000);
        }
    }
}

在这里插入图片描述

四、使用lua脚本

4.1 lua脚本引入

分布式锁setnx、setex的缺陷,在setnx和setex中间发生了服务down机

  • 从Redis宕机讲解分布式锁执行的异常场景流程

  • 从Server服务宕机讲解分布式锁执行的异常场景流程

  • 在setnx和setex中间发生了服务down机 那么key将没有超时时间 会一直存在,新的请求永远进不来

在这里插入图片描述

解决方案:由于setnx与setex是分步进行,那么我们将两步合成一步,放在同一个原子中即可采用Lua脚本

在这里插入图片描述

4.2 lua脚本实现分布式锁(setnx + 超时时间)

lua脚本实现分布式锁(setnx + 超时时间),代码执行如下:
在这里插入图片描述

单独提取到一个类 Distribution3 里面,如下:

public class Distribution3 {
    private static String script = "" +
            "local lockSet = redis.call('setnx', KEYS[1], ARGV[1])\n" +
            "if lockSet == 1 then\n" +
            "  redis.call('expire', KEYS[1], ARGV[2])\n" +
            "end\n" +
            "return lockSet";

    public static void main(String[] args) throws Exception {
        // region 分布式锁Lua脚本实现 (新建两个线程并运行,两个线程模拟两个服务端副本Pod)
        // 第一个线程 setnx 成功
        // 第二个线程 是隔断了 1s 去执行 setnx 命令,此时第一次 setnx 过期时间30s 还没有结束,所以第二个线程 setnx 返回为0
        for (int i = 0; i < 2; i++) {
            new Thread() {
                public void run() {
                    try {
                        //lua脚本分布式锁
                        Object result = JedisUtil.getJedisUtil().eval(script, Arrays.asList("lualock"), Arrays.asList("huihui", "30"));
                        System.out.println("打印:" + result.toString());
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }

                ;
            }.start();
            Thread.sleep(1000);
        }
        //endregion
    }
}

在这里插入图片描述

4.3 lua脚本实现分布式锁(setnx + del)

public class Distribution4 {
    private static String script = "" +
            "local lockSet = redis.call('setnx', KEYS[1], ARGV[1])\n" +
            "if lockSet == 1 then\n" +
            "  redis.call('del', KEYS[1])\n" +
            "end\n" +
            "return lockSet";

    public static void main(String[] args) throws Exception {
        // region 分布式锁Lua脚本实现 (新建两个线程并运行,两个线程模拟两个服务端副本Pod)
        // 第一个线程 setnx 成功
        // 第二个线程 是隔断了 1s 去执行 setnx 命令,此时第一次 setnx 过期时间30s 还没有结束,所以第二个线程 setnx 返回为0
        for (int i = 0; i < 2; i++) {
            new Thread() {
                public void run() {
                    try {
                        //lua脚本分布式锁
                        Object result = JedisUtil.getJedisUtil().eval(script, Arrays.asList("lualock2"), Arrays.asList("huihui"));
                        System.out.println("打印:" + result.toString());
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }

                ;
            }.start();
            Thread.sleep(1000);
        }
        //endregion
    }

}

在这里插入图片描述

五、尾声

实现分布式锁的两种方式(两个线程模拟两个副本Pod)
1、单单redis命令:setnx + expire/del 【lock/lock2】
2、lua脚本实现: 将 setnx + expire/del 放到lua脚本中,然后去执行lua脚本 【lualock/lualock2】

方式1:单独的setnx 和 expire 的命令确实是原子的,但是无法保证两个组合起来还是原子的,无法保证执行 setnx 和 expire 的是同一个线程;
方式2:整个lua脚本是原子的,单独的setnx 和 expire 的命令确实是原子的,但是两个组合起来就不是原子的了,现在把他们放到同一个 lua 脚本里面,setnx + expire 就变成了原子的,保证执行 setnx 和 expire 的是同一个线程。

Distribution1: 不使用lua脚本(setnx + 超时时间)
Distribution2: 不使用lua脚本(setnx + del)
Distribution3: 使用lua脚本实现分布式锁(setnx + 超时时间)
Distribution4: 使用lua脚本实现分布式锁(setnx + del)

Redis实现分布式锁,完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

祖母绿宝石

打赏一下

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

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

打赏作者

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

抵扣说明:

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

余额充值