加载中...
avatar
文章
42
标签
25
分类
21
首页
Java
Spring全家桶
  • Spring
  • SpringBoot
  • SpringCloud
JVM
源码
  • Mybatis
  • HashMap
归档
其他
  • 互联网电子书汇总
  • JAVA八股文指南
  • 历史
  • 相册
关于
Logo码农Stormling分布式系列(一) | Redis分布式锁
搜索
首页
Java
Spring全家桶
  • Spring
  • SpringBoot
  • SpringCloud
JVM
源码
  • Mybatis
  • HashMap
归档
其他
  • 互联网电子书汇总
  • JAVA八股文指南
  • 历史
  • 相册
关于

分布式系列(一) | Redis分布式锁

发表于2024-12-03|更新于2024-12-22|Redis分布式
|总字数:1.2k|阅读时长:4分钟|浏览量:

Redis实现分布式锁

乐尚代驾学习笔记-Redis司机抢单分布式锁

1、setnx+过期时间实现

在这里插入图片描述

@Override
public void testLock() {
    //从redis里面获取数据
   //1 获取当前锁  setnx
    Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock");

    //2 如果获取到锁,从redis获取数据 数据+1 放回redis里面
    if(ifAbsent) {
        //获取锁成功,执行业务代码
        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0
        String value = redisTemplate.opsForValue().get("num");
        //2.如果值为空则非法直接返回即可
        if (StringUtils.isBlank(value)) {
            return;
        }
        //3.对num值进行自增加一
        int num = Integer.parseInt(value);
        redisTemplate.opsForValue().set("num", String.valueOf(++num));

        //3 释放锁
        redisTemplate.delete("lock");
    } else {
        try {
            Thread.sleep(100);
            this.testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.1、问题:

  • 如果业务执行过程中心出现异常,导致锁无法释放

1.2、解决方案:

  • 设置过期时间,到时间之后自动释放锁
//1 获取当前锁  setnx  + 设置过期时间
//        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
 Boolean ifAbsent = 
         redisTemplate.opsForValue()
                 .setIfAbsent("lock", "lock",10, TimeUnit.SECONDS);

2、UUID防止误删

  • 使用setnx+过期时间实现分布式锁,存在问题:删除不是自己的锁,锁误删

场景:如果业务逻辑的执行时间是7s。执行流程如下

  1. index1业务逻辑没执行完,3秒后锁被自动释放。

  2. index2获取到锁,执行业务逻辑,3秒后锁被自动释放。

  3. index3获取到锁,执行业务逻辑

    index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁, 导致index3的业务只执行1s就被别人释放。

最终等于没锁的情况

在这里插入图片描述

//uuid防止误删
@Override
public void testLock() {
    //从redis里面获取数据
    String uuid = UUID.randomUUID().toString();
    //1 获取当前锁  setnx  + 设置过期时间
    //        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
    Boolean ifAbsent =
            redisTemplate.opsForValue()
                    .setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);

    //2 如果获取到锁,从redis获取数据 数据+1 放回redis里面
    if(ifAbsent) {
        //获取锁成功,执行业务代码
        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0
        String value = redisTemplate.opsForValue().get("num");
        //2.如果值为空则非法直接返回即可
        if (StringUtils.isBlank(value)) {
            return;
        }
        //3.对num值进行自增加一
        int num = Integer.parseInt(value);
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        //出现异常

        //3 释放锁
        String redisUuid = redisTemplate.opsForValue().get("lock");
        if(uuid.equals(redisUuid)) {
            redisTemplate.delete("lock");
        }
       
    } else {
        try {
            Thread.sleep(100);
            this.testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、LUA脚本保证原子性

  • 通过uuid防止误删,但是还是存在问题,不具备原子性的
    在这里插入图片描述
//lua脚本保证原子性
@Override
public void testLock() {
    //从redis里面获取数据
    String uuid = UUID.randomUUID().toString();
    //1 获取当前锁  setnx  + 设置过期时间
    //        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
    Boolean ifAbsent =
            redisTemplate.opsForValue()
                    .setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);

    //2 如果获取到锁,从redis获取数据 数据+1 放回redis里面
    if(ifAbsent) {
        //获取锁成功,执行业务代码
        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0
        String value = redisTemplate.opsForValue().get("num");
        //2.如果值为空则非法直接返回即可
        if (StringUtils.isBlank(value)) {
            return;
        }
        //3.对num值进行自增加一
        int num = Integer.parseInt(value);
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        //出现异常

        //3 释放锁 lua脚本实现
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        //lua脚本
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        redisScript.setScriptText(script);
        //设置返回结果
        redisScript.setResultType(Long.class);
        redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
        
    } else {
        try {
            Thread.sleep(100);
            this.testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4 总结

4.1、加锁

// 1. 从Redis中获取锁,set k1 v1 px 20000 nx
String uuid = UUID.randomUUID().toString();
Boolean lock = this.redisTemplate.opsForValue()
      .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);

4.2、使用lua释放锁

// 2. 释放锁 del
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 设置lua脚本返回的数据类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 设置lua脚本返回类型为Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);

4.3、重试

Thread.sleep(500); 
testLock();

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

第一个:互斥性,在任何时刻,只有一个客户端能持有锁。

第二个:不会发生死锁,即使有一个客户端在获取锁操作时候崩溃了,也能保证其他客户端能获取到锁。

第三个:解铃还须系铃人,解锁加锁必须同一个客户端操作。

第四个:加锁和解锁必须具备原子性

文章作者: stormling
文章链接: http://www.stormling.top/posts/28724.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 码农Stormling!
Redis分布式
cover of previous post
上一篇
分布式系列(二) | Redisson分布式锁
分布式锁1、概述 Redisson 是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。 Redisson 的宗旨是:促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。 2、四个条件为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 第一个:互斥性,在任何时刻,只有一个客户端能持有锁。 第二个:不会发生死锁,即使有一个客户端在获取锁操作时候崩溃了,也能保证其他客户端能获取到锁。 第三个:解铃还须系铃人,解锁加锁必须同一个客户端操作。 第四个:加锁和解锁必须具备原子性 3、引入依赖<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> </dependency> 4、配置类别创建RedissonConfig...
cover of next post
下一篇
数据库系列(一) | MongoDB 7.0+ 快速入门&整合SpringBoot
MongoDB7.0+ 快速入门1、MongoDB1.1、MongoDB 概念1.1.1、什么是MongoDBMongoDB 是在2007年由DoubleClick公司的几位核心成员开发出的一款分布式文档数据库,由C++语言编写。 目的是为了解决数据大量增长的时候系统的可扩展性和敏捷性。MongoDB要比传统的关系型数据库简单很多。 在MongoDB中数据主要的组织结构就是数据库、集合和文档,文档存储在集合当中,集合存储在数据库中。 MongoDB中每一条数据记录就是一个文档,数据结构由键值(key=>value)对组成。 文档类似于 JSON 对象,它的数据结构被叫做BSON(Binary...
相关推荐
cover
2022-01-08
Redis系列(一)| 单机集群搭建3主3从
Redis集群部署-单主机 6节点 3M+3S下载docker redis 镜像docker pull redis:4.0 # 或者直接下载最新版redis镜像 docker pull redis !!! 注意 redis 5.0以上创建集群不在使用ruby,因此如果使用最新版的redis 一下ruby部分内容直接跳过 在 /data下新建 redis-cluster 文件夹,然后创建 redis-cluster.tmpl 文件# bind 127.0.0.1 protected-mode no port ${PORT} daemonize no dir /data/redis appendonly yes cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 15000 cluster-announce-port ${PORT} cluster-announce-bus-port...
cover
2024-12-05
分布式系列(二) | Redisson分布式锁
分布式锁1、概述 Redisson 是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。 Redisson 的宗旨是:促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。 2、四个条件为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 第一个:互斥性,在任何时刻,只有一个客户端能持有锁。 第二个:不会发生死锁,即使有一个客户端在获取锁操作时候崩溃了,也能保证其他客户端能获取到锁。 第三个:解铃还须系铃人,解锁加锁必须同一个客户端操作。 第四个:加锁和解锁必须具备原子性 3、引入依赖<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> </dependency> 4、配置类别创建RedissonConfig...

评论
ValineGitalk
avatar
stormling
文章
42
标签
25
分类
21
Follow Me
公告
欢迎大家来到Stormling博客
目录
  1. 1. Redis实现分布式锁
    1. 1.1. 1、setnx+过期时间实现
      1. 1.1.1. 1.1、问题:
      2. 1.1.2. 1.2、解决方案:
    2. 1.2. 2、UUID防止误删
    3. 1.3. 3、LUA脚本保证原子性
    4. 1.4. 4 总结
      1. 1.4.1. 4.1、加锁
      2. 1.4.2. 4.2、使用lua释放锁
      3. 1.4.3. 4.3、重试
最新文章
面向八股文面试专场
面向八股文面试专场2025-01-22
【每日早报】-2025-01-21 - 星期二
【每日早报】-2025-01-21 - 星期二2025-01-21
规则引擎 Drools 8+ 快速入门
规则引擎 Drools 8+ 快速入门2024-12-11
数据库系列(二) | Mybatis Plus 3.0+快速入门
数据库系列(二) | Mybatis Plus 3.0+快速入门2024-12-09
分布式系列(二) | Redisson分布式锁
分布式系列(二) | Redisson分布式锁2024-12-05
©2019 - 2025 By stormling
码农Stormling程序员,关注公众号【码农Stormling】回复【面试】获取最全面试pdf
搜索
数据加载中