redisssion分布式锁

news2025/2/26 3:33:03

分布式锁的问题

基于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>

然后配置:

 @Configuration
 public class RedisConfig {
    @Bean
    public 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() 虽然看起来简单,但是底层释放的是当前锁,不会释放其他的锁

@Autowired
 private RedissonClient redissonClient;

 @Test
 void 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
    @ConditionalOnMissingBean
    public 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.coloradmin.cn/o/2040963.html

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

相关文章

Vue2 和 Vue3中EventBus使用差异

目录 前言一、EventBus 和 mitt 的对比二、Vue 2 中的 EventBus 使用实例2.1 创建 EventBus2.2 在组件中使用 EventBus2.2.1 组件 A - 发送事件2.2.2 组件 B - 监听事件 2.3 注意事项 三、Vue 3 中的 mitt 使用实例3.1 安装 mitt3.2 创建 mitt 实例3.3 在组件中使用 mitt3.3.1 …

【笔记】MSPM0G3507开发环境搭建——MSPM0G3507与RT_Thread(一)

环境搭建大体过程就不再赘述了&#xff0c;本文记录一下我刚开始搭建环境时踩过的坑以及一些不太懂的地方。后边会出MSPM0G3507RT-Thread 3.1.5相关的教程&#xff0c;感兴趣记得点点关注。 本篇使用立创地猛星MSPM0G3507开发板 参考文章&#xff1a; 【学习笔记一】搭建MSPM…

几种Word Embedding技术详解

NLP 中的词嵌入是一个重要术语&#xff0c;用于以实值向量的形式表示用于文本分析的单词。这是 NLP 的一项进步&#xff0c;提高了计算机更好地理解基于文本的内容的能力。它被认为是深度学习在解决具有挑战性的自然语言处理问题方面最重要的突破之一。 在这种方法中&#xff…

视觉SLAM中的数学基础:李群与李代数

在视觉SLAM&#xff08;Simultaneous Localization and Mapping&#xff09;中&#xff0c;理解和应用李群&#xff08;Lie Group&#xff09;与李代数&#xff08;Lie Algebra&#xff09;是非常关键的。李群与李代数为描述和处理空间中的连续变换&#xff08;如旋转和平移&am…

【OCR 学习笔记】二值化——局部阈值方法

二值化——局部阈值方法 自适应阈值算法Niblack算法Sauvola算法 自适应阈值算法 自适应阈值算法1用到了积分图&#xff08;Integral Image&#xff09;的概念。积分图中任意一点 ( x , y ) (x,y) (x,y)的值是从图左上角到该点形成的矩形区域内所有值的和。即&#xff1a; I (…

逻辑回归之鸢尾花数据集多分类任务

目录 1.导入数据 2.定义多分类模型 3.准备测试数据 4.绘制决策边界 对于多分类任务&#xff0c;其实就是多个二分类任务。 先分黑色(标签为1)和其他(标签为0)&#xff0c;在这个基础上再去分红色和绿色&#xff0c;此时就将红色标签设置为1&#xff0c;其他设置为0&#x…

关于 Lora中 Chirp Spread Spectrum(CSS)调制解调、发射接收以及同步估计的分析

本文结合相关论文对CSS信号的数学形式、调制解调、发射接收以及同步估计做了全面分析&#xff0c;希望有助于更好地理解lora信号 long-range (LoRa) modulation, also known as chirp spread spectrum (CSS) modulation, in LoRaWAN to ensure robust transmission over long d…

Unity(2022.3.38LTS) - 页面介绍

目录 A. 创建项目 B.Unity 编辑器页面 C. 自己点点 A. 创建项目 有多个编辑器版本的选择编辑器. 3D和2D的区别就是初始化的包不同,这些包打开项目之后都可以在自行下载,随意切换, B.Unity 编辑器页面 Unity 编辑器页面是一个高度集成且功能丰富的开发环境&#xff0c;为游…

ISE14.7后仿真、烧录教程

ISE14.7后仿真、烧录教程 ISE14.7后仿真、烧录教程 系统版本&#xff1a;win10&#xff0c;EDA工具版本&#xff1a;ISE14.7&#xff0c;modelsim SE 10.4&#xff0c;本文主要包含两部分内容&#xff0c;首先是基于ISE的后仿真&#xff0c;基于ISE和modelsim的联合后仿真&am…

C++简单界面设计

#include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {---------------------窗口设置----------------------this->setWindowTitle("南城贤子摄影工作室");//设置窗口标题this->setWindowIcon(QIcon("d:\\Pictures\\C…

上海悠远为您解析芯片管理系统的核心功能

在当今科技日新月异的时代&#xff0c;芯片作为信息技术的基石&#xff0c;其管理效率与安全性直接关系到整个系统的稳定运行与数据安全。因此&#xff0c;一个高效、智能的芯片管理系统成为了不可或缺的技术支撑。该系统通过集成多项核心技术&#xff0c;实现了对芯片从生产到…

统信UOSV20 安装redis

在线安装 在统信软件&#xff08;UOS&#xff09;上使用yum安装Redis&#xff0c;可以按照以下步骤进行&#xff1a; 打开终端。首先添加Redis的官方仓库&#xff0c;可以使用以下命令&#xff1a; sudo yum install epel-release安装Redis&#xff1a; sudo yum install r…

简单的spring boot tomcat版本升级

简单的spring boot tomcat版本升级 1. 需求 我们使用的springboot版本为2.3.8.RELEASE&#xff0c;对应的tomcat版本为9.0.41&#xff0c;公司tomcat对应版本发现攻击者可发送不完整的POST请求触发错误响应&#xff0c;从而可能导致获取其他用户先前请求的数据&#xff0c;造…

c# 直接使用c++ 类库文件

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

基于JSP的足球赛会管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPJavaBeansServlet 工具&#xff1a;Eclipse、Navicat、Maven 系统展示 首页 管理员功能模块 用…

Linux:动态库和静态库

静态库与动态库 A&#xff1a;静态库&#xff08;.a&#xff09;&#xff1a;程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。 B&#xff1a;动态库&#xff08;.so&#xff09;&#xff1a;程序在运行的时候才去链接动态库的代码&#…

Ubuntu24.04设置国内镜像软件源

参考文章&#xff1a; Ubuntu24.04更换源地址&#xff08;新版源更换方式&#xff09; - 陌路寒暄 一、禁用原来的软件源 Ubuntu24.04 的源地址配置文件发生改变&#xff0c;不再使用以前的 sources.list 文件&#xff0c;升级 24.04 之后&#xff0c;该文件内容变成了一行注…

Java | Leetcode Java题解之第334题递增的三元子序列

题目&#xff1a; 题解&#xff1a; class Solution {public boolean increasingTriplet(int[] nums) {int n nums.length;if (n < 3) {return false;}int first nums[0], second Integer.MAX_VALUE;for (int i 1; i < n; i) {int num nums[i];if (num > second…

日常网站优化:SEO的6项目日常例行工作

大部分人每天都会登录自己的网站&#xff0c;或者至少每周登录一次。但是&#xff0c;如果你是一家小企业或者团队的负责人&#xff0c;你可能会有很多其他的工作要做&#xff0c;相对的&#xff0c;就没有那么多的时间可以花在SEO上。 当然您也可以选择一个专业的团队&#x…

差异对比:云服务器PK物理服务器

【若您对以下内容感兴趣&#xff0c;欢迎联系或关注我们】 在服务器领域&#xff0c;云服务器和物理服务器是两种常见的选择&#xff0c;它们在多个方面存在明显的区别。 一、资源虚拟化 云服务器&#xff1a;基于物理服务器通过虚拟化技术构建而成&#xff0c;多个虚拟服务…