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

通过Lua脚本手写redis分布式锁

1、手写 Redis 分布式锁,包括上锁、解锁、自动续期。

此功能实现采用 Lua脚本实现,Lua脚本可以保证原子性。

setnx可以实现分布式锁,但是无法实现可重入锁,所以用hset来代替setnx实现可重入的分布式锁。

-- lock
if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 thenredis.call('hincrby',KEYS[1],ARGV[1],1)redis.call('expire',KEYS[1],ARGV[2])return 1
elsereturn 0
end
-- unlock
if redis.call('hexists',KEYS[1],ARGV[1]) == 0 thenreturn nil
elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 thenreturn redis.call('del',KEYS[1])
elsereturn 0
end
-- expire
if redis.call('hexists',KEYS[1],ARGV[1]) == 0 thenreturn redis.call('expire',KEYS[1],ARGV[2])
elsereturn 0
end

2、工具类如下:

/*** @author xxx* @descpription: 自定义redis分布式锁* @date 2024/7/24*/
public class MyRedissonLua implements Lock {/***  加锁脚本*/private static final String lockScript ="if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +"redis.call('hincrby',KEYS[1],ARGV[1],1)    " +"redis.call('expire',KEYS[1],ARGV[2])    " +"return 1 " +"else    " +"return 0 " +"end";/*** 解锁脚本*/private static final String unLockScript ="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then    " +"return nil " +"elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then    " +"return redis.call('del',KEYS[1]) " +"else    " +"return 0 " +"end";/***  续期脚本*/private static final String expireScript ="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then  " +"    return redis.call('EXPIRE',KEYS[1],ARGV[2])     " +"else " +"    return 0 " +"end";private StringRedisTemplate stringRedisTemplate;/*** KEYS[1]*/private String lockName;/*** ARGV[1]*/private String uuidValue;/*** ARGV[2]*/private Long expireTime;public MyRedissonLua(StringRedisTemplate stringRedisTemplate, String lockName,String uuid) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue = uuid + ":" + Thread.currentThread().getId();this.expireTime = 30L;}@Overridepublic void lock() {tryLock();}@Overridepublic void unlock() {Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(unLockScript, Long.class), Arrays.asList(lockName), uuidValue);System.out.println("unlock lockName:" + lockName + "\tuuidValue:" + uuidValue + "\t expireTime:" + expireTime);if(flag == null) {throw new IllegalMonitorStateException("释放锁异常");}else {System.out.println("释放锁成功");}}@Overridepublic boolean tryLock() {boolean result;try {result = tryLock(-1L, TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}return result;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {if (time == -1L){System.out.println("lockName:" + lockName + "\tuuidValue:" + uuidValue + "\t expireTime:" + expireTime);//可重入while (!stringRedisTemplate.execute(new DefaultRedisScript<>(lockScript, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))){TimeUnit.MILLISECONDS.sleep(60);}//后台扫描程序,检测key的ttl,来实现续期reExpire();return true;}return false;}private void reExpire() {//每 10s 续期一次new Timer().schedule(new TimerTask(){@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 续期");if (stringRedisTemplate.execute(new DefaultRedisScript<>(lockScript, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))){reExpire();}}},(this.expireTime * 1000) / 3);}@Overridepublic void lockInterruptibly() {}@Overridepublic Condition newCondition() {return null;}
}

3、由于实现分布式锁的方式有很多,故采用工厂模式

/*** @author xxx* @descpription: 工厂模式生产分布式锁* @date 2024/7/24*/
@Component
public class DistributedLockFactory {private String lockName;private String uuid;@Resourceprivate StringRedisTemplate stringRedisTemplate;public DistributedLockFactory() {this.uuid = IdUtil.simpleUUID();}public Lock getDistributedLock(String lockType){if (lockType == null) {return null;}if (lockType.equals("REDIS")){lockName = "zzyyRedisLock";return new MyRedissonLua(stringRedisTemplate, lockName,uuid);} else if (lockType.equals("ZOOKEEPER")) {lockName = "zzyyZookeeperLock";//...Zookeeper版本的分布式锁return null;}return null;}
}

4、业务代码

import cn.hutool.core.util.IdUtil;
import com.coco.service.ICardService;
import com.coco.utils.lua.DistributedLockFactory;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;/*** @author xxx* @descpription: * @date 2024/7/18*/
@Service
public class ICardServiceImpl implements ICardService {@Resourceprivate StringRedisTemplate stringRedisTemplate;private static final String KEY = "sale001";@Value("${server.port}")private String port;@Resourceprivate RedissonClient redissonClient;/*** 自定义的redis分布式锁*/
//    private Lock lock = new MyRedissonLua(stringRedisTemplate, "zzyyRedisLock");/*** 通过工厂获取自定义的redis分布式锁*/@Resourceprivate DistributedLockFactory distributedLockFactory;@Overridepublic String sale() {version7();return "success";}private void version7() {Lock lock = distributedLockFactory.getDistributedLock("REDIS");lock.lock();try{String countStr = stringRedisTemplate.opsForValue().get(KEY);Integer count = countStr == null ? 0 : Integer.parseInt(countStr);if (count > 0) {stringRedisTemplate.opsForValue().set(KEY,String.valueOf(--count));System.out.println("剩余库存为:" + stringRedisTemplate.opsForValue().get(KEY) + "服务端口:" + port);//演示自动续期try {TimeUnit.SECONDS.sleep(120);} catch (InterruptedException e) {throw new RuntimeException(e);}}else {System.out.println("没有库存了");}}finally {lock.unlock();}}/*** 可重入锁*/private void version6() {Lock lock = distributedLockFactory.getDistributedLock("REDIS");lock.lock();try{String countStr = stringRedisTemplate.opsForValue().get(KEY);Integer count = countStr == null ? 0 : Integer.parseInt(countStr);if (count > 0) {stringRedisTemplate.opsForValue().set(KEY,String.valueOf(--count));System.out.println("剩余库存为:" + stringRedisTemplate.opsForValue().get(KEY) + "服务端口:" + port);//可重入testReEntry();}else {System.out.println("没有库存了");}}finally {lock.unlock();}}/***  可重入锁*/private void testReEntry() {Lock lock = distributedLockFactory.getDistributedLock("REDIS");lock.lock();try {System.out.println("===========再次获取锁=============");} finally {lock.unlock();}}/*** 通过Lua脚本实现分布式锁解锁*/private void version5() {String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID() +":" + Thread.currentThread().getId();//分布式锁(自旋)while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,10,TimeUnit.SECONDS)) {try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}}try {String countStr = stringRedisTemplate.opsForValue().get(KEY);Integer count = countStr == null ? 0 : Integer.parseInt(countStr);if (count > 0) {stringRedisTemplate.opsForValue().set(KEY,String.valueOf(--count));System.out.println("剩余库存为:" + stringRedisTemplate.opsForValue().get(KEY) + "服务端口:" + port);}else {System.out.println("没有库存了");}} finally {String script ="if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),Arrays.asList(key), uuidValue);}}/*** 添加判断防止解锁解的不是同一把锁*/private void version4() {String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID()+":" + Thread.currentThread().getId();//分布式锁(自旋)while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,10,TimeUnit.SECONDS)) {try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}}try {String countStr = stringRedisTemplate.opsForValue().get(KEY);Integer count = countStr == null ? 0 : Integer.parseInt(countStr);if (count > 0) {stringRedisTemplate.opsForValue().set(KEY,String.valueOf(--count));System.out.println("剩余库存为:" + stringRedisTemplate.opsForValue().get(KEY) + "服务端口:" + port);}else {System.out.println("没有库存了");}} finally {if (Objects.equals(uuidValue, stringRedisTemplate.opsForValue().get(key))){stringRedisTemplate.delete(key);}}}/*** 通过setnx实现redis分布式锁*/private void version3() {String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID()+":" + Thread.currentThread().getId();//分布式锁Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);if (flag) {try {String countStr = stringRedisTemplate.opsForValue().get(KEY);Integer count = countStr == null ? 0 : Integer.parseInt(countStr);if (count > 0) {stringRedisTemplate.opsForValue().set(KEY,String.valueOf(--count));System.out.println("剩余库存为:" + stringRedisTemplate.opsForValue().get(KEY) + "服务端口:" + port);}else {System.out.println("没有库存了");}} finally {if (Objects.equals(uuidValue, stringRedisTemplate.opsForValue().get(key))){stringRedisTemplate.delete(key);}}}else {try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}sale();}}/*** juc的lock锁*/private void version2() {
//        lock.lock();
//        try {
//            Integer count = (Integer) redisTemplate.opsForValue().get(KEY);
//            if (count > 0) {
//                redisTemplate.opsForValue().decrement(KEY);
//                System.out.println("剩余库存为:" + redisTemplate.opsForValue().get(KEY) + "服务端口:" + port);
//            }else {
//                System.out.println("没有库存了");
//            }
//        } finally {
//            lock.unlock();
//        }}
}

相关文章:

通过Lua脚本手写redis分布式锁

1、手写 Redis 分布式锁&#xff0c;包括上锁、解锁、自动续期。 此功能实现采用 Lua脚本实现&#xff0c;Lua脚本可以保证原子性。 setnx可以实现分布式锁&#xff0c;但是无法实现可重入锁&#xff0c;所以用hset来代替setnx实现可重入的分布式锁。 -- lock if redis.call…...

解析银行个人征信系统

银行个人征信系统&#xff0c;也被称为个人信用信息基础数据库或金融信用信息基础数据库&#xff0c;是我国社会信用体系的重要基础设施。该系统由中国人民银行组织国内相关金融机构建立&#xff0c;旨在依法采集、整理、保存、加工自然人&#xff08;法人&#xff09;及其他组…...

AttributeError: ‘list‘ object has no attribute ‘text‘

AttributeError: ‘list‘ object has no attribute ‘text‘ 目录 AttributeError: ‘list‘ object has no attribute ‘text‘ 【常见模块错误】 【解决方案】 示例代码 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英…...

Codeforces Round 874 (Div. 3)(A~D题)

A. Musical Puzzle 思路: 用最少的长度为2的字符串按一定规则拼出s。规则是&#xff1a;前一个字符串的尾与后一个字符串的首相同。统计s中长度为2的不同字符串数量。 代码: #include<bits/stdc.h> #include <unordered_map> using namespace std; #define N 20…...

[Python][基础语法]详细讲解

目录 1.顺序语句2.条件语句3.缩进和代码块4.空语句 pass5.循环语句1.while2.for3.continue4.break ∞.积累 1.顺序语句 默认情况下&#xff0c;Python的代码执行顺序是按照从上到下的顺序&#xff0c;依次执行# 输出结果&#xff1a;"123" print("1") pri…...

Layui---输入事件

输入实时监听 //监听表单单选框复选框选择 form.on(radio, function (data) {console.log(data.value); //得到被选中的值 });//监听表单下拉菜单选择form.on(select, function (data) //监听表单下拉菜单选择form.on(select, function (data) ​ //监听表单复选框选择form.…...

甄选范文“论软件测试中缺陷管理及其应用”软考高级论文,系统架构设计师论文

论文真题 软件缺陷指的是计算机软件或程序中存在的某种破坏正常运行能力的问题、错误,或者隐藏的功能缺陷。缺陷的存在会导致软件产品在某种程度上不能满足用户的需要。在目前的软件开发过程中,缺陷是不可避免的。软件测试是发现缺陷的主要手段,其核心目标就是尽可能多地找…...

spring框架实现滑动验证码功能

spring框架实现滑动验证码功能 1. 整体描述2. 具体实现2.1 滑动验证码实体类2.2 滑动验证码登录VO2.3 滑动验证码接口返回类2.4 滑动验证码工具类2.5 滑动验证码Service2.6 滑动验证码Controller 3 工程源码4 总结 1. 整体描述 之前项目需要在验证码模块&#xff0c;增加滑动验…...

Pytorch使用教学8-张量的科学运算

在介绍完PyTorch中的广播运算后&#xff0c;继续为大家介绍PyTorch的内置数学运算&#xff1a; 首先对内置函数有一个功能印象&#xff0c;知道它的存在&#xff0c;使用时再查具体怎么用其次&#xff0c;我还会介绍PyTorch科学运算的注意事项与一些实用小技巧 1 基本数学运算…...

[Spring Boot]登录密码三种加密方式

简述 介绍其三种密码加密方法 1.SM2加密与验签 2.随机密码盐加密 3.MD5加密 推荐使用方法1&#xff0c;其次使用方法2&#xff0c;最不推荐的是方法3。方法3极其容易被密码字典破解&#xff0c;如果项目进行安全测试&#xff0c;通常是不允许的加密方式。 SM2加密与验签 引入…...

前端面试项目细节重难点分享(十三)

面试题提问&#xff1a;分享你最近做的这个项目&#xff0c;并讲讲该项目的重难点&#xff1f; 答&#xff1a;最近这个项目是一个二次迭代开发项目&#xff0c;迭代周期一年&#xff0c;在做这些任务需求时&#xff0c;确实有很多值得分享的印象深刻的点&#xff0c;我讲讲下面…...

每天五分钟深度学习:向量化方式完成逻辑回归m个样本的前向传播

本文重点 我们已经知道了向量化可以明显的加速程序的运行速度,本节课程将使用向量化来完成逻辑回归的前向传播,不使用一个for循环。 逻辑回归的前向传播 我们先来回忆一下逻辑回归的前向传播,如果我们有m个训练样本,首先对第一个样本进行预测,我们需要计算z,然后计算预…...

以线程完成并发的UDP服务端

网络(九)并发的UDP服务端 以线程完成功能 客户端 // todo UDP发送端 #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <stdlib.h> #include <string.h…...

linux c 特殊字符分割

/* * brief: 根据split_symbol分割字符串 * param: str为要分割的字符串&#xff0c;split_symbol是分隔符 * return&#xff1a;返回garray的指针数组&#xff0c;如果返回非空需要自己处理释放 */ GPtrArray_autoptr char_sz_spilt(pchar* str, pchar split_symbol) {if (NUL…...

搭建本地私有知识问答系统:MaxKB + Ollama + Llama3 (wsl网络代理配置、MaxKB-API访问配置)

目录 搭建本地私有知识问答系统:MaxKB、Ollama 和 Llama3 实现指南引言MaxKB+Ollama+Llama 3 Start buildingMaxKB 简介:1.1、docker部署 MaxKB(方法一)1.1.1、启用wls或是开启Hyper使用 WSL 2 的优势1.1.2、安装docker1.1.3、docker部署 MaxKB (Max Knowledge Base)MaxKB …...

谷粒商城实战笔记-65-商品服务-API-品牌管理-表单校验自定义校验器

文章目录 1&#xff0c;el-form品牌logo图片自定义显示2&#xff0c;重新导入和注册element-ui组件3&#xff0c;修改brand-add-or-update.vue控件的表单校验规则firstLetter 校验规则sort 校验规则 1&#xff0c;el-form品牌logo图片自定义显示 为了在品牌列表中自定义显示品…...

学好C++之——命名空间

c开始学习之时&#xff0c;你不可避免会遇到一个新朋友&#xff0c;那就是——namespace&#xff08;命名空间&#xff09;。 那么这篇文章就来为你解决这个小麻烦喽~ 目录 1.namespace存在的意义 2.namespace的定义 3.namespace的使用 1.namespace存在的意义 在C中&#…...

pytorch lightning报错all tensors to be on the same device

RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu! 修改指定为gpu trainer pl.Trainer(max_epochstrain_params.iterations, loggertb_logger,acceleratorgpu, devices1)...

Redis中的哨兵(Sentinel)

上篇文章我们讲述了Redis中的主从复制&#xff08;Redis分布式系统中的主从复制-CSDN博客&#xff09;&#xff0c;本篇文章针对主从复制中的问题引出Redis中的哨兵&#xff0c;希望本篇文章会对你有所帮助。 文章目录 一、引入哨兵机制 二、基本概念 三、主从复制的问题 四、哨…...

产业创新研究杂志产业创新研究杂志社产业创新研究编辑部2024年第12期目录

高质量发展 如何在新一轮产业链变革中平稳应对挑战 王宏利; 1-3《产业创新研究》投稿&#xff1a;cnqikantg126.com 基于ERGM的城市间绿色低碳技术专利转让网络结构及演化研究 吕彦朋;姜军;张宁; 4-6 数字基础设施建设对城市FDI的影响——基于“宽带中国”试点政策…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...