当前位置: 首页 > 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的影响——基于“宽带中国”试点政策…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙

Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

DAY 45 超大力王爱学Python

来自超大力王的友情提示&#xff1a;在用tensordoard的时候一定一定要用绝对位置&#xff0c;例如&#xff1a;tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾&#xff1a; tensorboard的发展历史和原理tens…...

react菜单,动态绑定点击事件,菜单分离出去单独的js文件,Ant框架

1、菜单文件treeTop.js // 顶部菜单 import { AppstoreOutlined, SettingOutlined } from ant-design/icons; // 定义菜单项数据 const treeTop [{label: Docker管理,key: 1,icon: <AppstoreOutlined />,url:"/docker/index"},{label: 权限管理,key: 2,icon:…...

C#中用于控制自定义特性(Attribute)

我们来详细解释一下 [AttributeUsage(AttributeTargets.Class, AllowMultiple false, Inherited false)] 这个 C# 属性。 在 C# 中&#xff0c;Attribute&#xff08;特性&#xff09;是一种用于向程序元素&#xff08;如类、方法、属性等&#xff09;添加元数据的机制。Attr…...

在ubuntu等linux系统上申请https证书

使用 Certbot 自动申请 安装 Certbot Certbot 是 Let’s Encrypt 官方推荐的自动化工具&#xff0c;支持多种操作系统和服务器环境。 在 Ubuntu/Debian 上&#xff1a; sudo apt update sudo apt install certbot申请证书 纯手动方式&#xff08;不自动配置&#xff09;&…...