Redis学习专题(六)分布式锁

目录

一、情景再现

二、分布式锁主流实现方案

三、实现Redis分布式锁

0、启动redis

[1、指令:setnx key value](#1、指令:setnx key value)

[2、指令:del key](#2、指令:del key)

[3、指令 :expire key seconds](#3、指令 :expire key seconds)

[4、指令:ttl key](#4、指令:ttl key)

[5、指令:set key value nx ex seconds](#5、指令:set key value nx ex seconds)

四、代码实现分布式锁

五、优化分布式锁

1、死锁

解决办法:设置过期时间

2、误删锁

解决办法:设置UUID

3、删除缺乏原子性

解决办法:LUA脚本保证删除原子性

思考:

核心区别:原子性​​


一、情景再现

  1. 单体单机部署的系统被演化成分布式集群系统后
  2. 由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效
  3. 单纯的Java API并不能提供分布式锁的能力
  4. 为了解决这个问题就需要一种跨JVM互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题
  5. 示意图(说明:我们探讨的分布式锁是针对分布式项目/架构而言[...],和redis集群无关)

单体项目锁机制示意图:

分布式项目锁机制示意图:

二、分布式锁主流实现方案

1、基于数据库实现分布式锁

2、基于缓存Redis实现分布式锁

3、基于Zookeeper实现分布式锁

性能对比:

性能:redis最高

可靠性:Zookeeper最高

三、实现Redis分布式锁

0、启动redis

1、指令:setnx key value

setnx:上锁

key:锁的键

value:锁的值

在这个key删除之前,不能执行相同key的上锁指令

2、指令:del key

删除key,也就是释放锁

3、指令 :expire key seconds

给锁key设置过期时间,目的防止**死锁,**一直不释放锁就会造成死锁

4、指令:ttl key

查看某个锁key的过期时间

5、指令:set key value nx ex seconds

设置锁的同时,指定该锁的过期时间,防止死锁。

这个指令的原子性 的,防止setnx key value/setnx key seconds两条指令中间执行被打断。

到期自动删除

四、代码实现分布式锁

情景再现:

在单机redis下,用springboot+redis实现分布式锁的使用

示意图:

当index1、index2、index3都想去执行业务逻辑的时候,先要到redis中获得锁 (执行setnx指令成功),然后执行业务逻辑 ,必须释放锁,不然后续的请求进不来造成死锁

1、先初始化数据

2、执行以下代码:

复制代码
    @GetMapping("/lock")
    public void lock() {
        //第一步:获取锁
        Boolean lock=redisTemplate.opsForValue().setIfAbsent("lock", "ok");
        if (lock){
            //这个key为num的要先设置默认值,否则会出现问题
            Object value = redisTemplate.opsForValue().get("num");
            //1.判断返回的value是否有值
            if(value == null || !StringUtils.hasText(value.toString())){
                return;
            }
            //2.有值就转成int
            int num = Integer.parseInt(value.toString());
            //3.将num+1再转回去
            redisTemplate.opsForValue().set("num", num + 1);
            //4.释放锁
            redisTemplate.delete("lock");
        }else {
            //获取锁失败,休眠100ms再此获取
            try {
                Thread.sleep(100);
                lock();//递归重新执行
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

3、保证Linux可以访问到springboot项目

4、用ab工具测试,指令:ab -n 1000 -c 100 http://ip:端口/api接口路径

请求1000次,每次按100请求

num就变成了1000

五、优化分布式锁

1、死锁

在前面的代码中,我们没有设置分布式锁的过期时间,如果因为业务异常没有释放锁,可能会导致死锁

解决办法:设置过期时间

2、误删锁

用户A业务操作因为卡顿,超过了设置的锁过期时间,于是自动释放了,此时用户B就获取到锁了,但是可能用户A的卡顿结束就会去释放锁,本次释放的锁就是用户B的锁了。

也就是步骤3释放了1的锁,步骤5释放了用户B在步骤4设置的锁

属于是释放锁时未验证当前锁是否仍属于自己

解决办法:设置UUID

1、给锁设置唯一的UUID

2、释放锁的时候查看是不是设置的同一把锁

3、造成这个问题的本质是缺乏原子性

3、删除缺乏原子性

用户A和用户B操作步骤如下:

1)比较uuid发现一样

2)准备删掉,但是还没有删除时,在1步获取的锁到了过期期间自动释放

3)同时因为高并发情况下,B用户执行了第4步,瞬间冲进来了,获取/设置了锁lock

4)这时del lock就是B用户的锁lock ,又出现了误删,也就是说A释放了B的锁

解决办法:LUA脚本保证删除原子性

解答:

用指令得到lock,这个值是当前请求的锁的值,ARGV是uuid值,如果相等就删除

执行

这个Array.asList("lock")会传递给script的KEYS[1],uuid会传给ARGV[1]

思考:

都是判断uuid和key的值是不是一样的,为什么直接比较uuid不行

核心区别:原子性​
​方案​ ​执行方式​ ​是否原子​ ​是否可能误删​
​Lua 脚本​ Redis 单线程执行(GET + DEL 一步完成) ✅ 是 ❌ 不会误删
​普通 Java 代码​ GET(Java端判断)→ 再 DEL(两步操作) ❌ 非原子 ✅ 可能误删

相关推荐
今天又在摸鱼3 小时前
rabbitmq的高级特性
分布式·rabbitmq
野犬寒鸦4 小时前
Redis核心数据结构操作指南:字符串、哈希、列表详解
数据结构·数据库·redis·后端·缓存·哈希算法
cui_win6 小时前
深入理解 Redis 哨兵模式
运维·redis·哨兵·哨兵故障
林晓lx7 小时前
[学习笔记] 从零开始虚拟化搭建数据库服务器
数据库·redis·笔记·centos
清幽竹客7 小时前
理解 Redis 事务-20 (MULTI、EXEC、DISCARD)
数据库·redis
龙哥·三年风水7 小时前
openresty+lua+redis把非正常访问的域名加入黑名单
redis·lua·openresty
LDM>W<8 小时前
黑马点评-分布式锁Lua脚本
java·分布式·lua
CET中电技术9 小时前
分布式光伏接入引起农村电压越限,如何处理?
分布式·光伏
鲸屿19510 小时前
kafka之操作示例
分布式·kafka