Spring Boot 中的 Redis 分布式锁

news2024/11/26 15:00:56

Spring Boot 中的 Redis 分布式锁

在分布式系统中,多个进程同时访问共享资源时,很容易出现并发问题。为了避免这些问题,我们可以使用分布式锁来保证共享资源的独占性。Redis 是一款非常流行的分布式缓存,它也提供了分布式锁的功能。在 Spring Boot 中,我们可以很容易地使用 Redis 分布式锁来管理并发访问。

本文将介绍 Redis 分布式锁的概念和原理,并说明如何在 Spring Boot 中使用它们。

在这里插入图片描述

Redis 分布式锁的概念和原理

Redis 分布式锁是一种基于 Redis 的分布式锁解决方案。它的原理是利用 Redis 的原子性操作实现锁的获取和释放,从而保证共享资源的独占性。

在 Redis 中,我们可以使用 setnx 命令来实现分布式锁。setnx 命令可以将一个键值对设置到 Redis 中,但只有在该键不存在的情况下才会设置成功。因此,我们可以将锁的获取和释放分别实现为 setnx 和 del 命令。

以下是一个基本的 Redis 分布式锁示例:

public class RedisDistributedLock {

    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final long LOCK_EXPIRE_TIME = 30000L;

    private RedisTemplate<String, Object> redisTemplate;
    private String lockKey;
    private String lockValue;
    private boolean locked = false;

    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String lockKey, String lockValue) {
        this.redisTemplate = redisTemplate;
        this.lockKey = LOCK_KEY_PREFIX + lockKey;
        this.lockValue = lockValue;
    }

    public boolean lock() {
        if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue)) {
            redisTemplate.expire(lockKey, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
            locked = true;
            return true;
        } else {
            return false;
        }
    }

    public void unlock() {
        if (locked) {
            redisTemplate.delete(lockKey);
        }
    }
}

在这个示例中,我们定义了一个 RedisDistributedLock 类,它包含了 lock 和 unlock 两个方法。lock 方法用于获取锁,unlock 方法用于释放锁。

lock 方法的实现逻辑如下:

  1. 使用 setIfAbsent 方法尝试将锁的键值对设置到 Redis 中。
  2. 如果设置成功,则调用 expire 方法设置锁的过期时间,并标记 locked 为 true。
  3. 如果设置失败,则说明锁已经被其他进程占用,返回 false。

unlock 方法的实现逻辑如下:

  1. 如果 locked 为 true,则使用 delete 方法删除锁的键值对。

Spring Boot 中的 Redis 分布式锁实现

在 Spring Boot 中,我们可以使用 RedisTemplate 来访问 Redis,并利用其提供的 setIfAbsent 和 delete 方法实现 Redis 分布式锁。

以下是一个基本的 Spring Boot + Redis 分布式锁示例:

@RestController
public class UserController {

    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public UserController(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") String id) throws InterruptedException {
        RedisDistributedLock lock = new RedisDistributedLock(redisTemplate, "user:" + id, UUID.randomUUID().toString());
        try {
            while (!lock.lock()) {
                Thread.sleep(100);
            }
            // 处理业务逻辑
            return new User();
        } finally {
            lock.unlock();
        }
    }
}

在这个示例中,我们定义了一个 UserController 类,其中包含了一个 getUser 方法,用于获取用户信息。在方法中,我们首先创建了一个 RedisDistributedLock 对象,然后在 while 循环中调用 lock 方法获取锁,直到获取成功为止。在获取锁后,我们可以执行相应的业务逻辑。最后,在 finally 块中调用 unlock 方法释放锁。

需要注意的是,由于 Redis 分布式锁是基于时间的,因此必须设置合适的过期时间。在示例中,我们将锁的过期时间设置为 30 秒。

Redis 分布式锁的优化

在实际应用中,Redis 分布式锁的性能和可靠性都非常重要。以下是几个优化 Redis 分布式锁的方法:

1. 使用 Lua 脚本

在上面的示例中,我们使用了两个 Redis 命令(setIfAbsent 和 expire)来实现分布式锁,这将导致两次网络通信。在高并发情况下,这会增加 Redis 的负载,影响性能。为了避免这个问题,我们可以使用 Lua 脚本来将这两个命令合并为一个原子操作。

以下是一个使用 Lua 脚本实现 Redis 分布式锁的示例:

public class RedisDistributedLock {

    private static final String LOCK_SCRIPT = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('pexpire', KEYS[1], ARGV[2]) return true else return false end";
    private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final long LOCK_EXPIRE_TIME = 30000L;

    private RedisTemplate<String, Object> redisTemplate;
    private String lockKey;
    private String lockValue;
    private boolean locked = false;
    private String lockScript;
    private String unlockScript;

    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String lockKey, String lockValue) {
        this.redisTemplate = redisTemplate;
        this.lockKey = LOCK_KEY_PREFIX + lockKey;
        this.lockValue = lockValue;
        this.lockScript = new DefaultRedisScript<>(LOCK_SCRIPT, Boolean.class).getScriptAsString();
        this.unlockScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class).getScriptAsString();
    }

    public boolean lock() {
        Object result = redisTemplate.execute(new DefaultRedisScript<>(lockScript, Boolean.class), Collections.singletonList(lockKey), lockValue, LOCK_EXPIRE_TIME);
        locked = (Boolean) result;
        return locked;
    }

    public void unlock() {
        if (locked) {
            redisTemplate.execute(new DefaultRedisScript<>(unlockScript, Long.class), Collections.singletonList(lockKey), lockValue);
        }
    }
}

在这个示例中,我们将 setIfAbsent 和 expire 命令合并为一个 Lua 脚本,并使用 RedisTemplate 的 execute 方法来执行脚本。在 lock 方法中,我们执行 LOCK_SCRIPT 脚本,如果返回 true,则表示获取锁成功。在 unlock 方法中,我们执行 UNLOCK_SCRIPT 脚本来释放锁。

使用 Lua 脚本可以减少 Redis 的网络通信次数,从而提高性能和可靠性。

2. 重试机制

由于分布式系统中存在网络抖动等问题,Redis 分布式锁的获取和释放可能会失败。为了提高可靠性,我们可以使用重试机制来重复执行获取锁和释放锁的操作。

以下是一个基本的重试机制实现示例:

public boolean lockWithRetry(int retryCount, long retryInterval) throws InterruptedException {
    int count = 0;
    while (count < retryCount) {
        if (lock()) {
            return true;
        }
        count++;
        Thread.sleep(retryInterval);
    }
    return false;
}

public void unlockWithRetry(int retryCount, long retryInterval) throws InterruptedException {
    int count = 0;
    while (count < retryCount) {
        try {
            unlock();
            break;
        } catch (Exception e) {
            count++;
            Thread.sleep(retryInterval);
        }
    }
}

在这个示例中,我们定义了 lockWithRetry 和 unlockWithRetry 两个方法。lockWithRetry 方法会重复执行 lock 方法,如果获取锁成功,则返回 true;否则,等待一段时间后重试。unlockWithRetry 方法会重复执行 unlock 方法,如果释放锁成功,则结束重试;否则,等待一段时间后重试。

使用重试机制可以提高 Redis 分布式锁的可靠性,保证在网络抖动等情况下仍能正常工作。

3. 采用 Redlock 算法

Redis 分布式锁的另一个问题是单点故障。由于 Redis 是单点的,如果 Redis 实例宕机,那么所有的分布式锁都会失效。为了解决这个问题,我们可以采用 Redlock 算法。

Redlock 算法是 Redis 官方提出的一种分布式锁算法。它通过使用多个 Redis 实例来避免单点故障的问题。具体来说,Redlock 算法首先获取多个 Redis 实例上的锁,然后比较这些锁的时间戳,选择时间戳最小的锁为有效锁。

以下是一个使用 Redlock 算法实现 Redis 分布式锁的示例:

public class RedisDistributedLock {

    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final long LOCK_EXPIRE_TIME = 30000L;
    private static final int RETRY_COUNT = 3;
    private static final long RETRY_INTERVAL = 100L;

    private RedisTemplate<String, Object> redisTemplate;
    private String lockKey;
    private String lockValue;
    private boolean locked = false;
    private List<RedisConnection> connections = new ArrayList<>();

    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String lockKey, String lockValue) {
        this.redisTemplate = redisTemplate;
        this.lockKey = LOCK_KEY_PREFIX + lockKey;
        this.lockValue = lockValue;
    }

    public boolean lock() {
        for (int i = 0; i < RETRY_COUNT; i++) {
            RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
            connections.add(connection);
            try {
                byte[] keyBytes = redisTemplate.getKeySerializer().serialize(lockKey);
                byte[] valueBytes = redisTemplate.getValueSerializer().serialize(lockValue);
                long expireTime = System.currentTimeMillis() + LOCK_EXPIRE_TIME + 1;
                for (int j = 0; j < connections.size(); j++) {
                    RedisConnection conn = connections.get(j);
                    if (j == connections.size() - 1) {
                        conn.set(keyBytes, valueBytes, Expiration.milliseconds(LOCK_EXPIRE_TIME), RedisStringCommands.SetOption.SET_IF_ABSENT);
                    } else {
                        conn.set(keyBytes, valueBytes, Expiration.milliseconds(LOCK_EXPIRE_TIME), RedisStringCommands.SetOption.SET_IF_ABSENT);
                        conn.pExpire(keyBytes, expireTime);
                    }
                }
                locked = true;
                return true;
            } catch (Exception e) {
                // ignore
            }
            try {
                Thread.sleep(RETRY_INTERVAL);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        return false;
    }

    public void unlock() {
        for (RedisConnection connection : connections) {
            try {
                connection.del(redisTemplate.getKeySerializer().serialize(lockKey));
            } catch (Exception e) {
                // ignore
            } finally {
                connection.close();
            }
        }
        connections.clear();
        locked = false;
    }
}

在这个示例中,我们定义了一个 RedisDistributedLock 类,它使用多个 RedisConnection 来实现 Redlock 算法。在 lock 方法中,我们首先获取多个 RedisConnection,然后在这些连接上分别执行 set 和 pExpire 命令。在执行完这些命令后,我们比较这些锁的时间戳,选择时间戳最小的锁为有效锁。在 unlock 方法中,我们分别释放每个 RedisConnection 上的锁。

使用 Redlock 算法可以提高 Redis 分布式锁的可靠性,避免单点故障的问题。

总结

在分布式系统中,使用分布式锁是保证共享资源独占性的重要方式。Redis 分布式锁是一种基于 Redis 的分布式锁解决方案,它通过利用 Redis 的原子性操作实现锁的获取和释放,从而保证共享资源的独占性。在 Spring Boot 中,我们可以很容易地使用 Redis 分布式锁,通过 RedisTemplate 来操作 Redis 实例。在使用 Redis 分布式锁时,需要注意锁的粒度、锁的超时时间、重试机制和 Redlock 算法,以保证分布式锁的可靠性和高可用性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/718558.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

react - TS

TypeScript TypeScript 简称『TS』&#xff0c;是微软开发的一个开源的编程语言。 一、TS 特点 TS 主要有如下几个特点: 完全兼容 JavaScript&#xff0c;是 JavaScript 的超集 引入类型系统&#xff0c;可以尽早的定位错误位置, 帮助提升开发效率 let obj {a:1,b:2....}o…

四氯化硅铝的活性离子蚀刻

引言 铝膜广泛用于集成电路中的互连线。随着电路的集成规模和密度变得越来越大&#xff0c;有必要将互连线和间隔的宽度减小到几乎薄膜厚度。为了实现这一点&#xff0c;已经开发了具有定向蚀刻能力的平行平面电极型等离子体蚀刻。这被称为反应离子蚀刻&#xff0c;因为它采用…

基于免疫优化算法的物流配送中心选址规划研究(Matlab实现)

目录 1 概述 2 物流配送中心选址规划研究 3 Matlab代码 4 结果 1 概述 影响物流配送中心选址的因素有很多,精确选址优化问题亟待解决。通过充分考虑货物的配送时间,将免疫算法加入其中,介绍了物流配送选址模型的构建以及免疫算法实现的相关步骤,最后利用matlab软件进行分析,提出…

手机端三维人体建模软件-易模,为虚拟现实、游戏开发带来新体验

三维人体建模是一种高级的数字技术&#xff0c;用于将人体的形态、肌肉、骨骼等三维信息转化为数字模型。这项技术涵盖了计算机图像处理、计算机辅助设计等领域&#xff0c;具有广泛的应用价值。通过三维人体建模&#xff0c;可以更好地理解人体的结构和功能&#xff0c;对于医…

弗迪科技携手纷享销客共建CRM系统,数智化升级加速“灯塔工厂”征程

当前&#xff0c;全球新一轮科技革命正和产业升级融合发展&#xff0c;数字化技术成为各行各业升级发展的重要推动力。 自2018年起&#xff0c;世界经济论坛与麦肯锡咨询公司发起“灯塔工厂”项目&#xff0c;全球严选制造业数字化转型典范作为“数字化制造”和“全球化4.0”的…

squid代理服务应用

squid代理服务器 代理的工作机制&#xff1a; 代理服务器的概念及其作用&#xff1a; 其主要作用有&#xff1a; Squid 代理的类型&#xff1a; 安装 Squid 服务 编译安装 Squid 修改 Squid 的配置文件 Squid 的运行控制 创建 Squid 服务脚本 构建传统代理服务器 生产环境…

红黑树:变色旋转规则化抽象逻辑分析

文章目录 一.红黑树的定义红黑树平衡性论证 二.红黑树的节点插入插入新节点后最小违规子结构(抽象分析)最小违规子结构一号的规则化算法分析最小违规子结构二号的规则化算法分析 三.红黑树类代码托管四.红黑树与AVL树的对比 旷世奇才发明的数据结构 一.红黑树的定义 红黑树的节…

【C语言初阶(10)】函数练习题

文章目录 1. 判断素数2. 判断闰年3. 函数实现二分查找4. 记录函数调用次数 1. 判断素数 题目内容 写一个函数可以判断一个数是不是素数。 素数 素数也叫做质数&#xff0c;一个只能被 1 和它本身整除的数字称之为素数。 例如&#xff1a;7 这个数字只能被 1 和 它本身&#x…

赛效:怎么将PPT转为PDF

1&#xff1a;在电脑网页上打开云组件&#xff0c;点击“PPT转换”菜单里的“PPT转PDF”。 2&#xff1a;点击“选择文件”可以将本地PPT文件添加上去。 3&#xff1a;文件添加成功后&#xff0c;点击下方的“开始转换”按钮。 4&#xff1a;文件转换成功后&#xff0c;在预览页…

vue动态修改浏览器标题和logo

问题描述 需要将一个系统&#xff0c;更改一下标题、logo&#xff0c;然后部署成另一个系统&#xff0c;由于不想单独拉出一套代码&#xff08;单独拉出来后维护成本增加&#xff09;&#xff0c;所以想要动态改变系统标题和图标 解决方案 将项目制造一个入口可以修改项目的…

20230703 -- scRNAseq from gastric cancer

文章标题&#xff1a;《Single-cell atlas of lineage states, tumor microenvironment and subtypespecific expression programs in gastric cancer》 DOI: 10.1158/2159-8290.CD-21-0683 数据集组织形式快照&#xff1a; step1 利用Seurat包整合数据 #! conda env R4libra…

Hombrew中AdoptOpenJDK已废弃更换Eclipse Temurin安装最新版JDK,并实现不同JDK版本之间切换

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Python基础 —— 运算符

每天提升一 。。。。。。 〇、概述 Python 中有很多运算符&#xff0c;大体分为 算数运算符、赋值远算符、比较运算符、逻辑运算符。通过这些运算符能够更好地完成一些列的数据运算。 一、算数运算符 1. 算数运算符 算数运算符 主要用于 数值类型&#xff08;整型、浮点型…

TCP三次和四次握手:

内容来自思学堂&#xff1a; TCP三次握手&#xff1a;确保双方都在线上 TCP四次握手&#xff1a;处理客户端要断开连接的需求

JavaWeb学习路线(11)—— Maven延伸

一、分模块设计 &#xff08;一&#xff09;概念&#xff1a; 将项目按功能拆分出若干个子模块。 &#xff08;二&#xff09;作用&#xff1a; 方便项目管理维护、扩展&#xff0c;也方便模块间相互调用&#xff0c;资源共享。 &#xff08;三&#xff09;具体实现 1、抽取…

深度学习项目实战二: LetNet5网络结构搭建

深度学习项目实战二: LetNet5网络结构搭建 文章目录 深度学习项目实战二: LetNet5网络结构搭建@[TOC](文章目录)一、卷积基本运算公式二、LetNet5网络1. 网络结构![在这里插入图片描述](https://img-blog.csdnimg.cn/0008fe6e5886414eac09eed49556ad99.png)2. 导入相关包3. 代码…

Apikit 自学日记:流程用例

添加普通用例 进入自动化测试用例管理页面&#xff0c;点击 添加用例 按钮&#xff0c;在弹窗中输入用例名称等信息&#xff0c;然后点击确定即可。 发起用例测试 创建好测试用例之后&#xff0c;点击 执行测试 按钮即可运行测试&#xff0c;系统会自动按顺序执行测试流程里的…

汽车下半年行情启动?概念全线爆发

2023年上半年&#xff0c;车市整体的基调是打价格战和加速“内卷”&#xff0c;在资本市场&#xff0c;汽车产业链相关概念股的表现整体也是不温不火。然而&#xff0c;下半年刚开始&#xff0c;汽车产业链股集体高调反弹。 7月4日&#xff0c;A股市场涨幅居前的概念板块几乎全…

MapstructPlus的快速集成

https://www.mapstruct.plus/https://www.mapstruct.plus/ # 博主技术栈如下 springboot:2.4.5 lombok:1.8.20 mapstruct-plus:1.3.4 knife4j:4.0.0目录 一、添加依赖&#xff08;谨防依赖冲突&#xff09; 二、如果依赖下不下来&#xff0c;要在maven的setting文件中加入腾讯…

NSS [SWPUCTF 2021 新生赛]no_wakeup

NSS [SWPUCTF 2021 新生赛]no_wakeup 先看题目&#xff0c;反序列化&#xff0c;绕过weakup。 exp&#xff1a; <?php class HaHaHa{public $admin;public $passwd;public function __construct(){$this->admin "admin";$this->passwd "wllm";…