当前位置: 首页 > news >正文

redisssion分布式锁

分布式锁的问题

基于setnx的分布式锁实现起来并不复杂,不过却存在一些问题。

锁误删问题

第一个问题就是锁误删问题,目前释放锁的操作是基于DEL,但是在极端情况下会出现问题。

例如,有线程1获取锁成功,并且执行完任务,正准备释放锁:

总结一下,误删的原因归根结底是因为什么?

  • 超时释放

  • 判断锁标示、删除锁两个动作不是原子操作

超时释放问题

除了上述问题以外,分布式锁还会碰到一些其它问题:

  • 锁的重入问题:同一个线程多次获取锁的场景,目前不支持,可能会导致死锁

  • 锁失败的重试问题:获取锁失败后要不要重试?目前是直接失败,不支持重试

  • Redis主从的一致性问题:由于主从同步存在延迟,当线程在主节点获取锁后,从节点可能未同步锁信息。如果此时主宕机,会出现锁失效情况。此时会有其它线程也获取锁成功。从而出现并发安全问题。

  • ...

当然,上述问题并非无法解决,只不过会比较麻烦。例如:

  • 原子性问题:可以利用Redis的LUA脚本来编写锁操作,确保原子性

  • 超时问题:利用WatchDog(看门狗)机制,获取锁成功时开启一个定时任务,在锁到期前自动续期,避免超时释放。而当服务宕机后,WatchDog跟着停止运行,不会导致死锁。

  • 锁重入问题:可以模拟Synchronized原理,放弃setnx,而是利用Redis的Hash结构来记录锁的持有者以及重入次数,获取锁时重入次数+1,释放锁是重入次数-1,次数为0则锁删除

  • 主从一致性问题:可以利用Redis官网推荐的RedLock机制来解决

快速入门

首先引入依赖:

<!--redisson-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId>
</dependency>

然后配置:

 @Configurationpublic class RedisConfig {@Beanpublic RedissonClient redissonClient() {// 配置类Config config = new Config();// 添加redis地址,这里添加了单点的地址,也可以使用config.useClusterServers()添加集群地址 config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123321");// 创建客户端return Redisson.create(config);}}

使用:

看门狗机制不能设置失效时间,设置失效时间看门狗会失效

lock.unlock() 虽然看起来简单,但是底层释放的是当前锁,不会释放其他的锁

@Autowiredprivate RedissonClient redissonClient;@Testvoid testRedisson() throws InterruptedException {// 1.获取锁对象,指定锁名称RLock lock = redissonClient.getLock("anyLock");try {// 2.尝试获取锁,参数:waitTime、leaseTime、时间单位// 设置失效时间看门狗会失效boolean isLock = lock.tryLock();if (!isLock) {// 获取锁失败处理 ..} else {// 获取锁成功处理}} finally {// 4.释放锁lock.unlock();}}

利用Redisson获取锁时可以传3个参数:

  • waitTime:获取锁的等待时间。当获取锁失败后可以多次重试,直到waitTime时间耗尽。waitTime默认-1,即失败后立刻返回,不重试。

  • leaseTime:锁超时释放时间。默认是30,同时会利用WatchDog来不断更新超时时间。需要注意的是,如果手动设置leaseTime值,会导致WatchDog失效。

  • TimeUnit:时间单位

集成

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.tianji.common.autoconfigure.redisson.aspect.LockAspect;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.time.Duration;
import java.util.ArrayList;
import java.util.List;@Slf4j
@ConditionalOnClass({RedissonClient.class, Redisson.class})
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedissonConfig {private static final String REDIS_PROTOCOL_PREFIX = "redis://";private static final String REDISS_PROTOCOL_PREFIX = "rediss://";@Bean@ConditionalOnMissingBeanpublic LockAspect lockAspect(RedissonClient redissonClient){return new LockAspect(redissonClient);}@Bean@ConditionalOnMissingBean    // 当spring容器中没有redissionClient 对象才会创建public RedissonClient redissonClient(RedisProperties properties){log.debug("尝试初始化RedissonClient");// 1.读取Redis配置RedisProperties.Cluster cluster = properties.getCluster();RedisProperties.Sentinel sentinel = properties.getSentinel();String password = properties.getPassword();int timeout = 3000;Duration d = properties.getTimeout();if(d != null){timeout = Long.valueOf(d.toMillis()).intValue();}// 2.设置Redisson配置Config config = new Config();if(cluster != null && !CollectionUtil.isEmpty(cluster.getNodes())){// 集群模式config.useClusterServers().addNodeAddress(convert(cluster.getNodes())).setConnectTimeout(timeout).setPassword(password);}else if(sentinel != null && !StrUtil.isEmpty(sentinel.getMaster())){// 哨兵模式config.useSentinelServers().setMasterName(sentinel.getMaster()).addSentinelAddress(convert(sentinel.getNodes())).setConnectTimeout(timeout).setDatabase(0).setPassword(password);}else{// 单机模式config.useSingleServer().setAddress(String.format("redis://%s:%d", properties.getHost(), properties.getPort())).setConnectTimeout(timeout).setDatabase(0).setPassword(password);}// 3.创建Redisson客户端return Redisson.create(config);}private String[] convert(List<String> nodesObject) {List<String> nodes = new ArrayList<>(nodesObject.size());for (String node : nodesObject) {if (!node.startsWith(REDIS_PROTOCOL_PREFIX) && !node.startsWith(REDISS_PROTOCOL_PREFIX)) {nodes.add(REDIS_PROTOCOL_PREFIX + node);} else {nodes.add(node);}}return nodes.toArray(new String[0]);}
}

几个关键点:

  • 这个配置上添加了条件注解@ConditionalOnClass({RedissonClient.class, Redisson.class}) 也就是说,引用了Redisson依赖,这套配置就会生效。不引入Redisson依赖,配置自然不会生效,从而实现按需引入。

  • RedissonClient的配置无需自定义Redis地址,而是直接基于SpringBoot中的Redis配置即可。而且不管是Redis单机、Redis集群、Redis哨兵模式都可以支持

所以,在微服务中应用的步骤:

  • 引入Redisson依赖

  • 注入RedissonClient,使用分布式锁

http://www.lryc.cn/news/424281.html

相关文章:

  • 嘎嘎嘎拿到去年想要的包
  • 前奏编曲:如何编写二段式前奏
  • 征服云端:Kubernetes如何让微服务与云原生技术如虎添翼
  • 开源AI智能名片系统与高级机器学习技术的融合应用:重塑商务交流的未来
  • Java中synchronized的偏向锁是如何减少锁开销的
  • react18 + ts 使用video.js 直播.m3u8格式的视频流
  • 使用 onBeforeRouteLeave 组合式函数提升应用的用户体验
  • uni-app 吸顶方案总结
  • 【C#】知识汇总
  • 1、Unity【基础】3D数学
  • 虚拟机ubuntu22的扩容记录
  • Docker 常用配置
  • 通过示例了解 .NET Core 中的依赖注入
  • fetch、FormData上传多张图片
  • C++STL详解(五)——list类的具体实现
  • 鸿蒙(API 12 Beta3版)【使用投播组件】案例应用
  • 【STM32项目】在FreeRtos背景下的实战项目的实现过程(一)
  • C#垃圾处理机制相关笔记
  • C语言memcmp函数
  • 低代码: 组件库测试之Vue环境下的测试工具以及测试环境搭建
  • 【Vue3】高颜值后台管理模板推荐
  • 详细介绍Pytorch中torchvision的相关使用
  • AI部署——主流模型推理部署框架
  • PyTorch之loading fbgemm.dll异常的解决办法
  • Vscode——如何实现 Ctrl+鼠标左键 跳转函数内部的方法
  • 力扣热题100_回溯_78_子集
  • 浏览器如何工作(一)进程架构
  • 【LeetCode】两数之和
  • UE5学习笔记11-为拿取武器添加动画
  • 68. 文本左右对齐【 力扣(LeetCode) 】