Redisson实现分布式锁

embedded/2024/12/21 14:30:28/

Redisson 是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

官方文档地址:https://github.com/Redisson/Redisson/wiki

1 概述

  • Redisson是一个在Redis的基础上实现的Java驻内存数据网格,封装很多功能,比如分布式锁,布隆过滤器,可以使用很简单方式实现这些功能

2 基本使用

1. 引入依赖

首先,我们需要在项目的pom.xml中添加Redisson和Nacos的依赖。以下是使用Maven的示例:

<!-- redisson -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.3</version> <!-- 根据最新版本选择 -->
</dependency>
<dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-spring-context</artifactId><version>2.0.3</version> <!-- 根据最新版本选择 -->
</dependency>

2. 创建Nacos配置

在nacos中进行配置管理

spring:  data:redis:host: 192.168.200.130port: 6379database: 0timeout: 1800000password:jedis:pool:max-active: 20 #最大连接数max-wait: -1    #最大阻塞等待时间(负数表示没限制)max-idle: 5    #最大空闲min-idle: 0     #最小空闲

3.创建配置类,初始redisson对象

java">package com.atguigu.tingshu.common.config.redssion;import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;/*** redisson配置信息*/
@Data
@Configuration
@ConfigurationProperties("spring.data.redis")
public class RedissonConfig {private String host;private String password;private String port;private int timeout = 3000;private static String ADDRESS_PREFIX = "redis://";/*** 自动装配**/@BeanRedissonClient redissonSingle() {Config config = new Config();if(StringUtils.isEmpty(host)){throw new RuntimeException("host is  empty");}SingleServerConfig serverConfig = config.useSingleServer().setAddress(ADDRESS_PREFIX + this.host + ":" + port).setTimeout(this.timeout);if(!StringUtils.isEmpty(this.password)) {serverConfig.setPassword(this.password);}return Redisson.create(config);}
}

整个流程的序列图如下:

4. 基本示例

java">// 阻塞式获取锁
RLock lock = redisson.getLock("myLock");
lock.lock(); //加锁//执行需要锁保护的代码
//执行需要锁保护的代码
//执行需要锁保护的代码
//执行需要锁保护的代码
//执行需要锁保护的代码lock.unlock(); //解锁// 非阻塞式获取锁
RLock lock = redisson.getLock("myLock");
if (lock.tryLock(10, TimeUnit.SECONDS)) {// 执行需要锁保护的代码lock.unlock();
} else {// 获取锁失败,执行其他操作
}

5. 测试代码

java">package com.atguigu.tingshu.album.service.impl;import com.alibaba.cloud.commons.lang.StringUtils;
import com.atguigu.tingshu.album.service.TestService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;@Service
public class TestServiceImpl implements TestService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedissonClient redissonClient;@Overridepublic void testLock() {//0.1 创建锁对象RLock lock = redissonClient.getLock("lock");//0.2 获取锁//0.2.1 一直等待到获取锁,如果获取锁成功,锁的有效时间为:30s,底层启动"看门狗"线程(如果业务有超时风险)可以延迟锁有效时间lock.lock();//0.2.2 一直等待到获取锁,如果获取锁成功,自定义锁有效时间//lock.lock(10, TimeUnit.SECONDS);//0.2.3 尝试获取锁 参数1:等待获取锁时间,超时则放弃获取  参数2:如果获取锁成功,锁的有效时间 参数3:时间单位//boolean b = lock.tryLock(3, 10, TimeUnit.SECONDS);try {//1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)String value = stringRedisTemplate.opsForValue().get("num");if (StringUtils.isBlank(value)) {return;}//2.对获取到值进行+1操作int num = Integer.parseInt(value);stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));} catch (NumberFormatException e) {e.printStackTrace();} finally {//3. 释放锁lock.unlock();}}
}

 整个业务,在查询一个专辑详情时,添加缓存和分布式锁代码

java">@Override
public AlbumInfo getAlbumInfoById(Long id) {//生成数据key,包含专辑idString albumKey = RedisConstant.ALBUM_INFO_PREFIX+ id;//1 查询redis缓存AlbumInfo albumInfo = (AlbumInfo)redisTemplate.opsForValue().get(albumKey);//2 如果redis查询不到数据,查询mysql,把mysql数据放到redis里面if(albumInfo == null) {// 查询mysql添加分布式锁,使用Redisson实现try {//获取锁对象String albumLockKey = RedisConstant.ALBUM_INFO_PREFIX+id+ ":lock";RLock rLock = redissonClient.getLock(albumLockKey);//加锁// rLock.lock(); 加锁,默认值//rLock.lock(30,TimeUnit.SECONDS); 自己设置过期时间// trylock三个参数:第一个等待时间,第二个参数过期时间boolean tryLock = rLock.tryLock(10, 30, TimeUnit.SECONDS);if(tryLock) { //加锁成功try {//查询mysql,AlbumInfo albumInfoData = this.getAlbumInfoData(id);if(albumInfoData == null) {albumInfoData = new AlbumInfo();//把mysql查询数据放到redis里面redisTemplate.opsForValue().set(albumKey,albumInfoData,RedisConstant.ALBUM_TIMEOUT,TimeUnit.SECONDS);}//把mysql查询数据放到redis里面redisTemplate.opsForValue().set(albumKey,albumInfoData,RedisConstant.ALBUM_TIMEOUT,TimeUnit.SECONDS);return albumInfoData;}finally {//解锁rLock.unlock();}} else {// 没有获取到锁的线程,自旋return getAlbumInfoById(id);}} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//3 如果redis查询数据,直接返回return albumInfo;}
}

3 两个机制

第一个:重试机制

  • 重试机制是由于各种原因导致的锁竞争失败,Redisson会自动进行重试,直到获取到锁或者超时。这个机制可以有效地避免因为网络问题等不可控因素导致的锁竞争失败。

第二个:看门狗机制(WatchDog)

  • 看门狗机制(WatchDog)是 指在获取锁之后,Redisson会启动一个守护线程来监控锁的情况,如果锁的过期时间即将到达,守护线程会自动续期。保证操作在有锁状态下执行

  • 看门狗机制是Redission提供的一种自动延期机制,这个机制使得 Redission提供的分布式锁是可以自动续期的。

  • 在Redission中想要启动看门狗机制,不要自己设置过期时间。如果自己定义了过期时间,无论是通过 lock 还是 tryLock 方法,都无法启用看门狗机制 。

4 使用优化 

在项目中很多数据接口需要查询缓存,那么分布式锁的业务逻辑代码就会出现大量的重复。因此,我们可以借助Spring框架事务注解来实现简化代码。只要类上添加了一个注解,那么这个注解就会自带分布式锁的功能。

实现如下:

在模块中添加一个自定义注解

java">@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GuiguCache {/*** 缓存key的前缀* @return*/String prefix() default "cache";
}

创建AOP切面类

java">@Aspect
@Component
public class GuiguCacheAspect {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate RedissonClient redissonClient;//当方法上有GuiguCache时候,这个方法执行@SneakyThrows@Around("@annotation(com.atguigu.tingshu.common.cache.GuiguCache)")public Object cacheAspect(ProceedingJoinPoint joinPoint) {//1 获取业务方法上注解里面前缀值 album:Info @GuiguCache(prefix="album:Info")MethodSignature signature = (MethodSignature)joinPoint.getSignature();GuiguCache guiguCache = signature.getMethod().getAnnotation(GuiguCache.class);String prefix = guiguCache.prefix();//2 获取业务方法里面参数值 id值  public AlbumInfo getAlbumInfoById(Long id)Object[] args = joinPoint.getArgs();//3 根据前缀值 + id值生成redis的keyString key = prefix+ Arrays.asList(args).toString();//4 查询redisObject o = redisTemplate.opsForValue().get(key);if(o == null) {try {//5 如果redis查询不到数据,查询mysql,把mysql数据放到redis里面,返回数据//5.1 添加分布式锁//获取锁对象RLock rLock = redissonClient.getLock(key + ":lock");//加锁boolean tryLock = rLock.tryLock(10, 30, TimeUnit.SECONDS);if(tryLock) {//加锁成功try {//查询mysqlObject obj = joinPoint.proceed(args);if (null == obj){// 并把结果放入缓存obj = new Object();this.redisTemplate.opsForValue().set(key, obj, 1,TimeUnit.HOURS);return obj;}redisTemplate.opsForValue().set(key,obj,1,TimeUnit.HOURS);return obj;} finally {rLock.unlock();}} else {//加锁失败return cacheAspect(joinPoint);}} catch (InterruptedException e) {throw new RuntimeException(e);}} else {//6 如果redis查询到数据,直接返回return o;}}
}

这样只要在实现类中添加该注解,设置前缀,redis里面key名字前缀 ,即可使用分布式

例如:

java">@GuiguCache(prefix = RedisConstant.ALBUM_INFO_PREFIX)
@Override
public AlbumInfo getAlbumInfoById(Long id) {return this.getAlbumInfoDB(id);
}@Override
@GuiGuCache(prefix = "albumStat:")
public AlbumStatVo getAlbumStatVoByAlbumId(Long albumId) {//	调用mapper 层方法return albumInfoMapper.selectAlbumStat(albumId);
}@GuiguCache(prefix = "category:")
@Override
public BaseCategoryView getCategoryViewByCategory3Id(Long category3Id) {return baseCategoryViewMapper.selectById(category3Id);
}


http://www.ppmy.cn/embedded/147548.html

相关文章

在线免费公共DNS解析服务器列表

在线免费公共DNS解析服务器列表&#xff1a;推荐阿里DNS、百度DNS、Google免费DNS地址、OpenDNS地址库、114 DNS、DNSPod等 公共DNS各地区公共DNS各地电信DNS各地联通DNS各地移动DNS各地铁通DNS教育网DNS美国DNS如果配置了不合理的 DNS 服务器&#xff0c;可能会导致网速缓慢、…

虚拟现实辅助工程技术在航空领域的应用

虚拟现实辅助工程技术应用于航空领域是当前和未来的重要发展趋势。虚拟现实辅助工程技术通过虚拟环境模拟航空结构在不同工作状态下的行为和性能演化&#xff0c;为结构的优化设计、高效率制造和低成本维护提供了全方位支持。 虚拟现实辅助工程技术在航空领域的应用 1.飞机结构…

OpenCV基本图像处理操作(三)——图像轮廓

轮廓 cv2.findContours(img,mode,method) mode:轮廓检索模式 RETR_EXTERNAL &#xff1a;只检索最外面的轮廓&#xff1b;RETR_LIST&#xff1a;检索所有的轮廓&#xff0c;并将其保存到一条链表当中&#xff1b;RETR_CCOMP&#xff1a;检索所有的轮廓&#xff0c;并将他们组…

Restaurants WebAPI(一)—— clean architecture

文章目录 项目地址一、Restaurants.Domain 核心业务层1.1 Entities实体层1.2 Repositories 数据操作EF的接口二、Restaurants.Infrastructure 基础设施层2.1 Persistence 数据EF CORE配置2.2 Repositories 数据查询实现2.3 Extensions 服务注册三、Restaurants.Application用例…

构建MacOS应用小白教程(打包 签名 公证 上架)

打包 在package.json中&#xff0c;dependencies会被打进 Electron 应用的包里&#xff0c;而devDependencies则不会&#xff0c;所以必要的依赖需要放到dependencies中。files中定义自己需要被打进 Electron 包里的文件。以下是一个完整的 mac electron-builder的配置文件。 …

STM32单片机芯片与内部33 ADC 单通道连续DMA

目录 一、ADC DMA配置——标准库 1、ADC配置 2、DMA配置 二、ADC DMA配置——HAL库 1、ADC配置 2、DMA配置 三、用户侧 1、DMA开关 &#xff08;1&#xff09;、标准库 &#xff08;2&#xff09;、HAL库 2、DMA乒乓 &#xff08;1&#xff09;、标准库 &#xff…

uniapp scroll-view 不生效排查

排查路径&#xff1a; 开启 refresher-enabled 属性使用竖向滚动时&#xff0c;需要给 <scroll-view> 一个固定高度&#xff0c;通过 css 设置 height&#xff1b;使用横向滚动时&#xff0c;需要给 <scroll-view> 添加 white-space: nowrap; 样式父元素不要设置 …

spring学习(XML中定义与配置bean(超详细)。IoC与DI入门spring案例)

目录 一、配置文件(XML)中定义与配置bean。 &#xff08;1&#xff09;bean的基础配置。&#xff08;id、class&#xff09; &#xff08;2&#xff09;bean的别名配置。 1、基本介绍。 2、demo测试。 3、NoSuchBeanDefinitionException&#xff01; &#xff08;3&#xff09;…