使用 Redisson 框架基于 Redis 实现分布式锁

news/2024/9/28 5:23:45

分布式锁可以采用数据库、zookeeper、redis 三种方式实现。

采用数据库实现方式,主要采用表字段的唯一索引特性。数据库是非常昂贵的资源,非常不推荐,最致命就是性能,不要去增加不必要的负担。

采用 zookeeper 的实现方式,主要使用其为客户端创建临时有序节点的特性,在我之前的博客有介绍。虽然使用 Apache Curator 客户端框架可以简化操作,但是其底层实现比较复杂,总体而言性能相对较差,因为需要维护大量的 Zookeeper 状态,引起大量网络 IO 的开销。

采用 redis 的实现方式,主要采用其单线程执行相关命令的特性。实现原理非常简单,性能也是最好,尤其采用 Redisson 实现方案。

Redisson 是基于 Redis 实现的一个框架。充分的利用了 Redis 键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包,获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度,简化了分布式环境中程序相互之间的协作。Redisson已经内置提供了基于Redis的分布式锁实现,此种方式是我们推荐的分布式锁使用方式。

本篇博客介绍 redis 的两种分布式锁的实现方式:自己编码实现方式和采用 redisson 框架的实现方式,在博客的最后会提供源代码下载。

Redisson 的官网访问地址为:https://redisson.org


一、搭建工程

新建一个 springboot 工程,取名为 springboot_redisson,工程结构如下图所示:

image

首先查看一下 pom 文件的内容,主要是引入了 redis 和 redisson 的 starter 依赖包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.jobs</groupId><artifactId>springboot_redisson</artifactId><version>1.0</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--引入 redis 的 starter 依赖包--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--引入 redisson 的 starter 依赖包--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.30.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.5</version></plugin></plugins></build>
</project>

然后查看一下 application.yml 的配置内容,主要是 redis 的连接信息配置

server:port: 8888
spring:redis:host: 192.168.136.128port: 6379password: rootjedis:pool:# 最大连接数max-active: 10# 最大空闲连接数max-idle: 5# 最小空闲min-idle: 1# 连接超时时间(毫秒)max-wait: 3000

二、代码细节

如果使用 RedisTemplate 操作 redis 的话,需要编写一个配置类,主要是改变一下 key 的序列化方式,方便明文查看

package com.jobs.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();//默认的Key序列化器为:JdkSerializationRedisSerializer//这里只是将 key 采用 string 序列化,方便查看redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setConnectionFactory(connectionFactory);redisTemplate.setEnableTransactionSupport(true);return redisTemplate;}
}

最后就是编写一个 controller 类,里面有 redis 和 redisson 两种实现分布式锁的方案

package com.jobs.controller;import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/lock")
public class LockController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 使用 redis 的两个命令实现分布式加锁和解锁* 加锁:set lock_key lock_value NX PX 3000* 解锁:del lock_key* 该分布式锁方案,是由我们自己编写代码实现,可以简单的实现分布式锁的特性,* 主要的不足时:这里是非阻塞锁,没有重试机制,获取不到锁后,直接返回失败,不太符合大多数应用场景*/@GetMapping("/redistest")public String stock1() {String result;//获取当前线程的idString threadId = String.valueOf(Thread.currentThread().getId());//尝试加锁,如果获取锁成功,则返回 true//为了防止死锁,获取到锁之后,给锁设置了一个 3 秒的有效期,过期自动释放锁//key 可以随便定义,value 是线程idBoolean locked = redisTemplate.opsForValue().setIfAbsent("mylock", threadId, 3, TimeUnit.SECONDS);if (locked) {try {//由于是 demo,这里就不从数据库中获取库存量了,以 redis 代替数据库获取库存量String temp = redisTemplate.opsForValue().get("stock");if (StringUtils.hasText(temp)) {int stock = Integer.parseInt(temp);if (stock > 0) {stock--;redisTemplate.opsForValue().set("stock", String.valueOf(stock));result = "库存量扣减成功,剩余库存量:" + stock;System.out.println(result);} else {result = "库存不足!!!";System.out.println(result);}} else {result = "请提前在 redis 中设置好 stock 库存量的值";System.out.println(result);}} catch (Exception ex) {result = ex.getMessage();System.out.println(result);} finally {//对比 redis 中的 value 值,如果是当前线程 id 才可以进行解锁String myValue = redisTemplate.opsForValue().get("mylock");if (threadId.equals(myValue)) {redisTemplate.delete("mylock");}}} else {result = "没有获取到锁,不能扣减库存量!!!";System.out.println(result);}return result;}//--------------------------------------------------@Autowiredprivate RedissonClient redissonClient;/*** 使用 Redission 框架实现分布式锁功能,具有阻塞重试的特性,非常适合绝大多数应用场景。*/@GetMapping("/redissontest")public String stock2() {String result;//获得分布式锁对象,这里还没有尝试去获取锁RLock lock = redissonClient.getLock("mylock");//尝试获取锁,如果获取成功,则后续程序继续执行;如果获取不成功则阻塞等待//如果获取锁成功,则锁的有效期是 3 秒,超时后自动解锁lock.lock(3, TimeUnit.SECONDS);try {//由于是 demo,这里就不从数据库中获取库存量了,以 redis 代替数据库获取库存量String temp = redisTemplate.opsForValue().get("stock");if (StringUtils.hasText(temp)) {int stock = Integer.parseInt(temp);if (stock > 0) {stock--;redisTemplate.opsForValue().set("stock", String.valueOf(stock));result = "库存量扣减成功,剩余库存量:" + stock;System.out.println(result);} else {result = "库存不足!!!";System.out.println(result);}} else {result = "请提前在 redis 中设置好 stock 库存量的值";System.out.println(result);}} catch (Exception ex) {result = ex.getMessage();System.out.println(result);} finally {//解锁lock.unlock();}return result;}
}

代码已经编写完毕,注释也比较详细,应该很容易理解。可以打包后部署多份,采用 nginx 进行负载均衡转发,然后采用 Jmeter 等压力测试工具,模拟出多线程进行访问请求,测试分布式锁的实现结果。这里就不进行展示了。


本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_redisson.zip

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

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

相关文章

笛卡尔树学习笔记

笛卡尔树 引入 是一种二叉树,每个节点由一个二元组 \((k,w)\) 形成。\(k\) 满足二叉搜索树的性质,\(w\) 满足堆的性质。上面这棵笛卡尔树相当于把数组元素值当作键值 \(w\),而把数组下标当作键值 \(k\)。显然可以发现,这棵树的键值 \(k\) 满足二叉搜索树的性质,而键值 \(w…

ImDisk高级指南:打造你的专属虚拟磁盘空间

ImDisk使用详解和高级用法一、ImDisk使用详解创建虚拟磁盘:使用命令行参数创建虚拟磁盘。例如,imdisk -a -s 10m -m B: 命令将创建一个大小为10MB的虚拟磁盘,并将其分配给B盘符。 你可以使用 -s 参数指定虚拟磁盘的大小,支持的单位包括b、k、m、g、t等,或者使用%表示可用内…

STM CubeMx不能生成代码的解决方法

在使用STM CubeMx时,遇到不能生成代码的问题,即点击“GENERATE CODE”后,软件没有任何反应。 从网上找到若干解决方案,大概是: 以下是可能的解决方法: 1. 确保你已经安装了正确版本的Keil和STM32CubeMX,并且它们都能正常运行。 2. 在STM32CubeMX中点击生成代码按钮之前,…

利用系统IO读取磁盘上指定BMP图片的宽和高以及大小

文件IO代码 /*************************************************************************************** file name: 1.c* author : lu.ciana.598393@gmail.com* date : 2024/05/11* function : 利用系统IO读取磁盘上指定BMP图片的宽和高以及大小* note : n…

雨天的尾巴

[Vani有约会] 雨天的尾巴 /【模板】线段树合并 题目背景 深绘里一直很讨厌雨天。 灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。 虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一…

[单机]完美国际_V155_GM工具_VM虚拟机

[端游] 完美国际单机版V155一键端PC电脑网络游戏完美世界幻海凌云家园 本教程仅限学习使用,禁止商用,一切后果与本人无关,此声明具有法律效应!!!! 教程是本人亲自搭建成功的,绝对是完整可运行的,踩过的坑都给你们填上了。 如果你是小白也没问题,跟着教程走也是可以搭…

产业园区越来越卷

在经济不断发展和转型升级的大背景下,产业园区作为推动区域经济发展的重要引擎之一,扮演着越来越重要的角色,亦得到了政府、产业巨头、“轻资产”运营商、创投机构等各方力量的持续关注!纵观2023年,产业园区现状如何,在招商、运营、数智化建设等方面,又该如何拨云见日,…

Spring MVC执行流程

视图执行流程用户发送出请求到前端控制器DispatcherServlet。 DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。 HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。 DispatcherServlet调用HandlerAdapter(…