Redis分布式锁的实现(Jedis和Redisson两个方案)

2023-11-05

应用场景
分布式锁主要用于解决,公司中不同业务系统对同一功能的数据产生脏读或重复插入。

比如公司现有三个小组分别开发WAP站、小程序、APP客户端,而这三个系统都存在领红包功能。

业务要求每人每日只能领取一个红包,如果有人同时登陆三个系统那么就能够同一时间领取到三个红包。

分布式锁的要求
分布式锁要满足以下基本要求:

共享锁。多系统能够共享同一个锁机制。
互斥性。在任意时刻,只有一个请求能持有锁。
无死锁。在程序崩溃时能够,自动释放锁。
持有者解锁。锁只能被加锁的请求解锁,其他请求无法解锁。
Jedis实现分布式锁
本例参考了博文:https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/

例子已上传码云:https://gitee.com/imlichao/jedis-distributed-lock-example

添加依赖
本例使用spring boot提供的redis实现,并没有直接引入jedis依赖。这样做的好处是,可以在项目中同时使用Jedis和RedisTemplate实例。

pom.xml文件

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

配置文件
application.properties文件

#redis

spring.redis.database=0
spring.redis.host=18.6.8.22
spring.redis.password=Mfqy_redis_password_
spring.redis.port=6379
#连接超时时间(项目或连接池链接redis超时的时间)
spring.redis.timeout=2000
#最大连接数(建议为 业务期望QPS/单个连接的QPS,50000/1000=50)
spring.redis.pool.max-active=50
#最大空闲链接数(为减小伸缩产生的性能消耗,建议和最大连接数设成一致的)
spring.redis.pool.max-idle=50
#最小空闲连接数(0代表在无请求的状况下从不创建链接)
spring.redis.pool.min-idle=0
#连接池占满后无法获取连接时的阻塞时间(超时后抛出异常)
spring.redis.pool.max-wait=3000

Jedis工厂类
由于我们使用了spring boot提供的redis实现,所以我们不能直接获取到jedis对象。Jedis工厂类从RedisConnectionFactory中获取Redis连接(JedisConnection实现类),然后使用反射的方法从中取得了Jedis实例。

/**
 * Jedis工厂类(单例模式)
 */
@Service
public class JedisFactory {
    @Autowired
    private RedisConnectionFactory connectionFactory;

    private JedisFactory(){}

    private static Jedis jedis;
    /**
     *  获得jedis对象
     */
    public Jedis getJedis() {
        //从RedisConnectionFactory中获取Redis连接(JedisConnection实现类),然后使用反射的方法从中取得了Jedis实例
        if(jedis == null){
            Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
            ReflectionUtils.makeAccessible(jedisField);
            jedis = (Jedis) ReflectionUtils.getField(jedisField, connectionFactory.getConnection());
        }
        return jedis;
    }
}

为避免死锁的发生,加锁和设定失效时间必须是一个原子性操作。否则一旦在加锁后程序出错,没能够执行设置失效时间的方法时,就会产生死锁。 但是RedisTemplate屏蔽了插入数据和设置失效时间同时执行的方法,我们只能获取到Jedis实例来执行。

分布式锁实现类
分布式锁主要实现了两个方法即占用锁和释放锁。

这里需要注意占用锁和释放锁都要保证原子性操作,避免程序异常时产生死锁。

锁id主要用于标识持有锁的请求,在释放琐时用来判断只有持有正确锁id的请求才能执行解锁操作。

/**
 * redis分布式锁
 */
@Service
public class DistributedLock {

    @Autowired
    private JedisFactory JedisFactory;

    /**
     * 占用锁
     * @param lockKey 锁key
     * @return 锁id
     */
    public String occupyDistributedLock(String lockKey) {
        //获得jedis实例
        Jedis jedis = JedisFactory.getJedis();
        //锁id(必须拥有此id才能释放锁)
        String lockId = UUID.randomUUID().toString();
        //占用锁同时设置失效时间
        String isSuccees = jedis.set(lockKey, lockId, "NX","PX", 15000);
        //占用锁成功返回锁id,否则返回null
        if("OK".equals(isSuccees)){
            return lockId;
        }else{
            return null;
        }
    }

    /**
     * 释放锁
     * @param lockKey 锁key
     * @param lockId 加锁id
     */
    public void releaseDistributedLock(String lockKey,String lockId) {
        if(lockId != null){
            //获得jedis实例
            Jedis jedis = JedisFactory.getJedis();
            //执行Lua代码删除lockId匹配的锁
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockId));
        }
    }
}

解释一下jedis.set(lockKey, lockId, “NX”,“PX”, 15000)方法

格式 - String set(String key, String value, String nxxx, String expx, long time);
功能 - 存储数据到缓存中,并制定过期时间和当Key存在时是否覆盖。
参数 -
key :redis key
value : redis值
nxxx: 只能取NX或者XX,如果取NX,则只有当key不存在是才进行set,如果取XX,则只有当key已经存在时才进行set
expx: 只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒。
time: 过期时间,单位是expx所代表的单位。

测试代码
Controller

/**
 * 分布式锁测试类
 */
@Controller
public class DistributedLockController {
    @Autowired
    private DistributedLock distributedLock;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(){
        return "/index";
    }

    @RequestMapping(value = "/occupyDistributedLock", method = RequestMethod.GET)
    public String occupyDistributedLock(RedirectAttributes redirectAttributes, HttpServletRequest request){
        String key = "userid:55689";

        String lockId = null;
        try{
            //占用锁
            lockId = distributedLock.occupyDistributedLock(key);
            if(lockId != null){
                //程序执行
                TimeUnit.SECONDS.sleep(10);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //释放锁
            distributedLock.releaseDistributedLock(key,lockId);
        }
        redirectAttributes.addFlashAttribute("lockId",lockId);

        return "redirect:/";
    }
}

页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>distributed lock</title>
</head>
<body>
<h1>分布式锁测试</h1>
<button onclick="window.location.href = '/occupyDistributedLock'">占用锁</button>
<br/>
<!-- 占用成功返回锁id -->
<#if lockId??>${lockId}</#if>
</body>
</html>

Redisson实现分布式锁(推荐)
使用Redisson提供的分布式锁更加方便,而且锁的具体细节也不需要考虑。

例子已上传码云:https://gitee.com/imlichao/redisson-distributed-lock-example

官网:https://redisson.org/

文档:https://github.com/redisson/redisson/wiki

SpringBoot配置
添加依赖

spring boot 中引用专用依赖,会自动生成配置和spring bean的实例。

pom.xml文件

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.10.1</version>
</dependency>

配置文件

application.properties文件

#redisson
spring.redis.database=0
spring.redis.host=13.6.8.1
spring.redis.password=Mfqy_redis
spring.redis.port=6379

测试代码

Controller

/**
 * 分布式锁测试类
 */
@Controller
public class DistributedLockController {
    @Autowired
    private RedissonClient redisson ;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(){
        return "/index";
    }

    @RequestMapping(value = "/occupyDistributedLock", method = RequestMethod.GET)
    public String occupyDistributedLock(RedirectAttributes redirectAttributes){
        RLock lock = null;
        try{
            //锁的key
            String key = "MF:DISTRIBUTEDLOCK:S:personId_1001";
            //获得分布式锁实例
            lock = redisson.getLock(key);
            //加锁并且设置自动失效时间15秒
            lock.lock(15, TimeUnit.SECONDS);
            //程序执行
            TimeUnit.SECONDS.sleep(10);
            //获取网络时间(多服务器测试统一时间)
            URL url=new URL("http://www.baidu.com");
            URLConnection conn=url.openConnection();
            conn.connect();
            long dateL=conn.getDate();
            Date date=new Date(dateL);
            //打印和返回结果
            System.out.println(date);
            redirectAttributes.addFlashAttribute("success",date);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //释放锁
            if (lock != null) lock.unlock();
        }

        return "redirect:/";
    }
}

页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>distributed lock</title>
</head>
<body>
<h1>分布式锁测试</h1>
<button onclick="window.location.href = '/occupyDistributedLock'">占用锁</button>
<br/>
<#if success??>${success?datetime}</#if>
</body>
</html>

手动配置
添加依赖

pom.xml文件

<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.10.1</version>
</dependency>

配置文件

application.properties文件

#redisson
spring.redis.database=0
spring.redis.host=13.6.8.1
spring.redis.password=Mfqy_redis
spring.redis.port=6379

配置类

/**
 * Redisson配置
 */
@Configuration
public class RedissonConfig {

    @Value("${spring.redis.database}")
    private int database;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private String port;

    @Bean
    RedissonClient createConfig() {
        Config config = new Config();
        //设置编码方式为 Jackson JSON 编码(不设置默认也是这个)
        config.setCodec(new JsonJacksonCodec());
        //云托管模式设置(我们公司用的阿里云redis产品)
        config.useReplicatedServers()
                //节点地址设置
                .addNodeAddress("redis://"+host+":"+port)
                //密码
                .setPassword(password)
                //数据库编号(默认0)
                .setDatabase(database);

        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

测试代码与SpringBoot配置一样

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis分布式锁的实现(Jedis和Redisson两个方案) 的相关文章

  • 使用 Sentinels 升级 Redis 的最佳实践?

    我有 3 个 Redis 节点 由 3 个哨兵监视 我进行了搜索 文档似乎不清楚如何最好地升级此类配置 我目前使用的是 3 0 6 版本 我想升级到最新的 5 0 5 我对这方面的程序有几个疑问 升级两个大版本可以吗 我在我们的暂存环境中执
  • Redis是如何实现高吞吐量和高性能的?

    我知道这是一个非常普遍的问题 但是 我想了解允许 Redis 或 MemCached Cassandra 等缓存 以惊人的性能极限工作的主要架构决策是什么 如何维持连接 连接是 TCP 还是 HTTP 我知道它完全是用C写的 内存是如何管理
  • StackExchange.Redis的正确使用方法

    这个想法是使用更少的连接和更好的性能 连接会随时过期吗 对于另一个问题 redis GetDatabase 打开新连接 private static ConnectionMultiplexer redis private static ID
  • 如何使 Redis 缓存中数据层次结构(树)的部分内容无效

    我有一些产品数据 需要在 Redis 缓存中存储多个版本 数据由 JSON 序列化对象组成 获取普通 基本 数据的过程很昂贵 将其定制为不同版本的过程也很昂贵 因此我想缓存所有版本以尽可能进行优化 数据结构看起来像这样 BaseProduc
  • 想要在后台不间断地运行redis-server

    我已经下载了 redis 2 6 16 tar gz 文件并安装成功 安装后我运行 src redis server 它工作正常 但我不想每次都手动运行 src redis server 而是希望 redis server 作为后台进程持续
  • 如何在Redis中只保存一个数据库?

    我是 Redis 新手 有一个与备份相关的问题 目前 我有一个实例在 Windows 服务器上运行 在这个实例中 我当前有一项 工作 将数据存储在一个数据库中 我不想备份这些数据 我必须创造一份新工作 我的第一个想法是将数据存储在另一个数据
  • 如何将“.csv”数据文件导入Redis数据库

    如何将 csv 数据文件导入 Redis 数据库 csv 文件中包含 id 时间 纬度 经度 列 您能否向我建议导入 CSV 文件并能够执行空间查询的最佳方法 这是一个非常广泛的问题 因为我们不知道您想要什么数据结构 您期望什么查询等等 为
  • 当 Jedis 与 Spring Data 一起使用时,为什么数据会以奇怪的键存储在 Redis 中?

    我将 Spring Data Redis 与 Jedis 一起使用 我正在尝试存储带有密钥的哈希值vc list id 我能够成功插入到redis 但是 当我使用 redis cli 检查密钥时 我没有看到密钥vc 501381 相反我看到
  • 由于配置文件错误,无法启动 Redis 服务器

    我刚刚按照此处的说明安装了 Redis http redis io download http redis io download 当我运行 redis server redis conf 时出现以下错误 FATAL CONFIG FILE
  • 使用环境变量在 redis.conf 中设置动态路径

    我有一个环境变量MY HOME其中有一个目录的路径 home abc 现在 我有一个redis conf文件 我需要像这样设置这个路径 redis conf pidfile MY HOME local var pids redis pid
  • Redis 中存储整数和字符串的区别

    这两个命令有什么区别吗 LPUSH myset 123 LPUSH myset 123 我想存储大约 500 万个整数 并且我想以最有效的方式做到这一点 不 没有什么区别 两者都存储为字符串 从redis io http redis io
  • 如何在Redis中正确存储图片?

    决定将图像存储在Redis中 如何正确执行 现在我这样做 redis gt set image path here is the base64 image code 我不确定这是否正常 将图片存储在Redis中是完全可以的 Redis 键和
  • 集合成员的 TTL

    Redis 是否可以不为特定键而是为集合的成员设置 TTL 生存时间 我正在使用 Redis 文档提出的标签结构 数据是简单的键值对 标签是包含与每个标签对应的键的集合 例如 gt SETEX id id 1 100 Lorem ipsum
  • 使用 Redis 中的键

    我是 Redis 和键值数据库的新手 你能告诉我如何在redis中正确实现这种关系方法吗 我有一个关系表 其中两个键对应一个值 master id slave id 价值 Example 主站 ID 从属ID 价值 1 1 值1 2 1 值
  • 检查 Redis 列表中是否已存在某个值

    我想知道是否有办法检查 redis 列表中是否已存在某个键 我无法使用集合 因为我不想强制唯一性 但我确实希望能够检查字符串是否确实存在 Thanks 您的选择如下 Using LREM如果发现则更换它 维护一个单独的SET与您的LIST
  • redis能完全取代mysql吗?

    简单的问题 我是否可以使用 redis 而不是 mysql 来处理各种 Web 应用程序 社交网络 地理位置服务等 IT 领域没有什么是不可能的 但有些事情可能会变得极其复杂 将键值存储用于全文搜索之类的事情可能会非常痛苦 另外 据我所知
  • memcache、redis 和 ehcache 作为分布式缓存框架的比较 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我需要做出的决定之一是在我的系统中使用什么缓存框架 有这么多可供选择 我目前正在研究 redis ehcache 和 memcached
  • Microsoft.Extensions.Caching.Redis 选择与 db0 不同的数据库

    一个关于了解使用哪个redis数据库以及如何配置它的问题 我有一个默认值ASP NET Core Web 应用程序和默认配置的本地redis服务器 含15个数据库 通过包管理控制台我已经安装了 Install Package Microso
  • spring中如何使用jackson代替JdkSerializationRedisSerializer

    我在我的一个 Java 应用程序中使用 Redis 并且正在序列化要存储在 Redis 中的对象列表 但是 我注意到使用 RedisTemplate 会使用 JdkSerializationRedisSerializer 相反 我想使用 J
  • 如果没有过期的内容,Redis maxmemory-policy volatile-lru 是否会被驱逐?

    我有一个 redis 服务器 设置了maxmemory policy set to volatile lru 文档表明 当达到内存限制时 这将从设置过期的条目集中逐出 在这种情况下 redis 是否只驱逐过期的项目 如果内存中的所有内容都设

随机推荐

  • 网络安全实验室4.注入关

    4 注入关 1 最简单的SQL注入 url http lab1 xseclab com sqli2 3265b4852c13383560327d1c31550b60 index php 查看源代码 登录名为admin 最简单的SQL注入 登
  • Could not autowire. No beans of 'OrderService' type found. less... (Ctrl+F1) Inspection info:Checks

    解决方法 步骤如下 把最后一个 去掉 点击ok就好了 当然还有一点就是 sevice类的前面加上 service注解
  • BRAM资源不够用?不怕!这里有FPGA BRAM省资源小秘招!

    FPGA的BRAM和LUT等资源都是有限的 在FPGA开发过程中 可能经常遇到BRAM或者LUT资源不够用的情况 一般建议BRAM和LUT资源的消耗不要超过80 当然高端一点的FPGA芯片也可以放宽到90 超过这个限制 可能就会出现时序违例
  • wifi中断攻击

    前言 wifi攻击包括wifi中断攻击 wifi密码爆破 wifi钓鱼等 本文章持续更新以上内容 先以wifi中断攻击为始 硬件为ESP8266 还买了一个无线网卡用作后续wifi密码爆破 网上某宝十几块钱就能玩 我买的是带Micro接口
  • Git的简述

    Git 文章目录 Git Git概述 版本控制工具 集中式管理控制工具 分步式管理控制工具 控制机制 Git和代码托管中心 安装Git软件 Git常用命令 Git概述 Git是一个免费的 开源的分步式版本控制系统 可以快速的处理从小型到大型
  • 输入一个url会发生什么事情?

    输入一个url会发生什么事情 1 输入解析 1 1 如果输入的是非url结构的字符串 则会使用默认浏览器的搜多引擎搜索这个字符串 1 2如果输入是url结构的字符串 url将通过进程建通信发送给网络进程 网络进程会进行DNS解析得到对应的I
  • 数据库基础 (关系数据库)

    14 34 属性 表的列 域 属性的范围 笛卡尔积 所有集合所有数X一遍 目或度 关系表属性的数量 候选码 唯一属性标识 主码 主键 外码 外键 主属性 候选码里的都叫主属性 全码 所有属性都是候选码 称为全码 关系的三种形式 基本关系 基
  • 国标GB28181视频监控平台EasyGBS视频无法播放,抓包返回ICMP是什么原因?

    国标GB28181视频平台EasyGBS是基于国标GB T28181协议的行业内安防视频流媒体能力平台 可实现的视频功能包括 实时监控直播 录像 检索与回看 语音对讲 云存储 告警 平台级联等功能 国标GB28181视频监控平台部署简单 可
  • 大数据手册(Spark)--Spark基础知识(PySpark版)

    文章目录 Spark 初始化 弹性分布式数据集 RDD DataFrame Spark安装配置 Spark基本概念 Spark基础知识 PySpark版 Spark机器学习 PySpark版 Spark流数据处理 PySpark版 Spar
  • Python-netfilterqueue(白帽)[netfilterqueue怎么安装在kali中?][iptables怎么用]

    一 环境安装 前要 使用python3 6以上版本安装netfilterqueue会出现报错无法安装 1 安装python3 6 前提 本机已有python3 9 在此前提下安装python3 6 问题 中间有什么报错无法进行 一般是小问题
  • C语言自动抓取淘宝商品详情网页数据,实现轻松高效爬虫

    你是否曾经遇到过需要大量获取网页上的数据 但手动复制粘贴又太过费时费力 那么这篇文章就是为你而写 今天我们将会详细讨论如何使用C语言实现自动抓取网页上的数据 本文将会从以下8个方面进行逐步分析讨论 1 HTTP协议的基本原理 在开始之前 我
  • Java 自动单元测试生成框架

    推广博客 Java 自动单元测试生成框架
  • STC15单片机自带的AD功能的使用

    一 什么是ADC DAC ADC Analog to Digital Converter的缩写 意思是模 数转换器 实现把模拟信号转变为数字量的设备称为模 数 A D 转换器 简称ADC 实现把数字量转变为模拟量的设备称为数 模 D A 转
  • PHP 登录注册附带邮箱手机号验证

    原创 请勿转载 PHP 登录注册页面 前言 一 配置数据库文件 二 登录注册代码段 1 登录 2 注册 3 主页 4 注销 总结 前言 php简单的登录页面与注册页面 有邮箱手机号验证 源码在github上 https github com
  • 双亲委派机制

    JVM双亲委派机制 JVM预定义的三种类型类加载器 定义 类加载器 ClassLoader 是Java语言的一项创新 也是Java流行的一个重要原因 在类加载的第一阶段 加载 过程中 需要通过一个类的全限定名来获取定义此类的二进制字节流 完
  • 关于SQL注入报错:Illegal mix of collations for operation ‘UNION‘原因剖析与验证

    关于SQL注入报错 Illegal mix of collations for operation UNION 原因剖析与验证 今天练习了一下DVWA的SQL注入模块 使用了union注入时报错如下 Illegal mix of colla
  • 解决“vue-router子路由默认视图不显示”问题

    今天在看Vue学习视频 老师讲解vue router多级路由的使用 在写完视频里的例子后 自己测试发现一个小问题 当时视频里没有提及 我是用name实现的路由之间的切换 其中一个有子路由 发现子路由的默认显示没有了 第一次默认有 第二次点击
  • JVM GC算法 CMS 详解(转)

    前言 CMS 全称Concurrent Low Pause Collector 是jdk1 4后期版本开始引入的新gc算法 在jdk5和jdk6中得到了进一步改进 它的主要适合场景是对响应时间的重要性需求 大于对吞吐量的要求 能够承受垃圾回
  • Python标准库asyncio模块基本原理浅析

    Python标准库asyncio模块基本原理浅析 本文环境python3 7 0 asyncio模块的实现思路 当前编程语言都开始在语言层面上 开始简化对异步程序的编程过程 其中Python中也开始了在语言层面上对异步编程的简化 特地使用了
  • Redis分布式锁的实现(Jedis和Redisson两个方案)

    应用场景 分布式锁主要用于解决 公司中不同业务系统对同一功能的数据产生脏读或重复插入 比如公司现有三个小组分别开发WAP站 小程序 APP客户端 而这三个系统都存在领红包功能 业务要求每人每日只能领取一个红包 如果有人同时登陆三个系统那么就