node.js Redis SETNX命令实现分布式锁解决超卖/定时任务重复执行问题

Redis SETNX 特性

当然,让我们通过一个简单的例子,使用 Redis CLI(命令行界面)来模拟获取锁和释放锁的过程。 在此示例中,我将使用键“lock:tcaccount_[pk]”和“status:tcaccount_[pk]”分别表示锁定键和状态键。

  1. 获取锁:
# 首先,设置锁密钥的唯一值和过期时间(秒)
127.0.0.1:6379> SET lock:tcaccount_1234 unique_value NX EX 3
OK

这里,“unique_value”是与锁关联的唯一标识符的占位符(生产环境UUID,随字符串),“EX 3”将过期时间设置为 3 秒

  1. 在另一个会话或请求中检查并获取锁:
# 其次,检查锁key是否存在,不存在则获取锁
127.0.0.1:6379> SET lock:tcaccount_1234 unique_value NX EX 3
(nil)

第二次尝试返回 nil,因为锁已经存在。 在真实的应用程序中,您将检查结果,如果结果为零,您可能会转到下一个帐户或等待并重试。

  1. 释放锁:
# 通过删除锁定密钥来解除锁定
127.0.0.1:6379> DEL lock:tcaccount_1234
(integer) 1

The DEL 命令用于删除锁键,有效释放锁。 返回的整数值 1 表示删除了一个键。

请注意,这是一个简化的示例,在现实场景中,您通常会使用脚本(例如 Lua 脚本)来使锁的获取和释放原子化,从而防止竞争条件。 这里的示例旨在说明使用 Redis 命令进行锁定的基本原理。

Node.js 程序中集成

node -v # v16.20.2
npm install redis # 笔者版本"redis": "^4.2.0"

client.eval() 方法lua脚本如何正确传参

let result = await client.eval('return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', {keys: ['key1', 'key2'],arguments: ['first', 'second']
}); 
//result =  [ 'key1', 'key2', 'first', 'second' ]

加锁实现

   const client = await createClient().on('error', err => console.log('Redis Client Error', err)).connect();async function lock(resourceKey, uniqueValue, expireTime = 10) {// 锁的键和值const lockKey = `lock:${resourceKey}`;/*   这种方式不能实现const result =   await client.setEx(lockKey, expireTime, uniqueValue);if (result === 'OK') {console.log(`[s] 已获取锁 ${resourceKey}`);return true;} else {console.log(`[x] 无法获取锁 ${resourceKey}`);return false;}
*/// Lua脚本用于原子获取锁const luaScript = `if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) thenreturn 1elsereturn 0end`;// 执行Lua脚本const result = await client.eval(luaScript, {keys: [lockKey],arguments: [uniqueValue, `${expireTime}`]});if (result === 1) {console.log(`[s] 已获取锁 ${resourceKey}`);return true;} else {console.log(`[x] 无法获取锁 ${resourceKey}`);return false;}}

请添加图片描述

释放锁的实现

释放锁时需要验证value值,也就是说我们在获取锁的时候需要设置一个value,不能直接用del key这种粗暴的方式,因为直接del key任何客户端都可以进行解锁了,所以解锁时,我们需要判断锁是否是自己的,基于value值来判断,代码如下

 
/*** 释放锁* @param resourceKey 资源键名* @param uniqueValue 唯一值,用于验证锁的所有者(建议:UUID)* @returns 是否成功释放锁*/async function unlock(resource, uniqueValue) {const lockKey = `lock:${resource}`;const luaScript = `if redis.call("GET", KEYS[1]) == ARGV[1] thenreturn redis.call("DEL", KEYS[1])elsereturn 0end`;const result = await client.eval(luaScript, {keys: [lockKey],arguments: [uniqueValue]});if (result === 1) {console.log('[s] 锁释放成功');} else {console.log('[x] 锁释放失败,可能锁已经被其他客户端更新');}}

应用场景

在这里插入图片描述
多台机器定时任务
订单超卖

完整脚本如下

const {createClient} = require('redis');
const {generateUUID} = require("../models/utl");(async ()=> {const client = await createClient().on('error', err => console.log('Redis Client Error', err)).connect();async function lock(resourceKey, uniqueValue, expireTime = 10) {// 锁的键和值const lockKey = `lock:${resourceKey}`;/*   const result =   await client.setEx(lockKey, expireTime, uniqueValue);if (result === 'OK') {console.log(`[s] 已获取锁 ${resourceKey}`);return true;} else {console.log(`[x] 无法获取锁 ${resourceKey}`);return false;}
*/// Lua脚本用于原子获取锁const luaScript = `if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) thenreturn 1elsereturn 0end`;// 执行Lua脚本const result = await client.eval(luaScript, {keys: [lockKey],arguments: [uniqueValue, `${expireTime}`]});if (result === 1) {console.log(`[s] 已获取锁 ${resourceKey}`);return true;} else {console.log(`[x] 无法获取锁 ${resourceKey}`);return false;}}async function unlock(resource, uniqueValue) {const lockKey = `lock:${resource}`;const luaScript = `if redis.call("GET", KEYS[1]) == ARGV[1] thenreturn redis.call("DEL", KEYS[1])elsereturn 0end`;const result = await client.eval(luaScript, {keys: [lockKey],arguments: [uniqueValue]});if (result === 1) {console.log('[s] 锁释放成功');} else {console.log('[x] 锁释放失败,可能锁已经被其他客户端更新');}}async function exampleUsage(resource) {const uniqueValue = generateUUID();const isLockAcquired = await lock(resource, uniqueValue);if (isLockAcquired) {try {// 在这里执行受锁保护的代码// 模拟一些处理时间await new Promise(resolve => setTimeout(resolve, 5000));} finally {// 最后释放锁unlock(resource, uniqueValue);}} else {console.log('[x] 未获取锁。 另一个进程可能正在持有锁。');}}const resourcePk = 'account_id123'let taskList = []for (let i = 0; i < 10; i++) {taskList.push( exampleUsage(resourcePk))}//并发拿同一账号await Promise.all(taskList);await new Promise(resolve => setTimeout(resolve, 6000));//测试重新获取锁await exampleUsage(resourcePk);})()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/651449.shtml

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

相关文章

AAC解码算法原理

关于更多音视频开发内容&#xff0c;请参考专栏音视频开发 AAC&#xff08;Advanced Audio Coding&#xff09;是一种高级音频编码标准&#xff0c;它是一种十分流行的音频压缩格式&#xff0c;通常用于存储和传输音频数据。AAC提供了高音质和高压缩效率&#xff0c;广泛应用于…

Android源码设计模式解析与实战第2版笔记(四)

第三章 自由扩展你的项目–Builder 模式 Builder 模式的定义 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 Builder 模式的使用场景 相同的方法&#xff0c;不同的执行顺序&#xff0c;产生不同的事件结果时 多个部件或零件&…

Android 基础技术——Handler

笔者希望做一个系列&#xff0c;整理 Android 基础技术&#xff0c;本章是关于 Handler 为什么一个线程对应一个Looper&#xff1f; 核心&#xff1a;通过ThreadLocal保证 Looper.prepare的时候&#xff0c;ThreadLocal.get如果不空报异常&#xff1b;否则调用ThreadLocal.set,…

Cesium渲染白膜数据

async DrawBaiMoFun2() {// tiles 矩阵变换let changePostion = (tileSet, tx, ty, tz, rx, ry, rz, scale, center) => {if (!center) return;const m = Cesium.Transforms.eastNorthUpToFixedFrame(center);const surface =center ||Cesium.Cartesian3.fromRadians(cartog…

自动驾驶代客泊车AVP决策规划详细设计

背景 随着产品的不断迭代&#xff0c;外部停车场的铺开&#xff0c;PAVP车辆需要应对的场景将越来越复杂&#xff0c;因此整体算法泛化能力的提升显得尤为关键。为了打磨巡航规划的能力&#xff0c;算法架构应当设计的更为灵活&#xff0c;可以针对使用场景迁入更为先进有效的算…

【Linux】分区向左扩容的方法

文章目录 为什么是向左扩容操作前的备份方法&#xff1a;启动盘试用Ubuntu后进行操作 为什么是向左扩容 Linux向右扩容非常简单&#xff0c;无论是系统自带的disks工具还是apt安装的gparted工具&#xff0c;都有图像化的界面可以操作。但是&#xff0c;都不支持向左扩容。笔者…

01 Redis的特性+下载安装启动+Redis自动启动+客户端连接

1.1 NoSQL NoSQL&#xff08;“non-relational”&#xff0c; “Not Only SQL”&#xff09;&#xff0c;泛指非关系型的数据库。 键值存储数据库 &#xff1a; 就像 Map 一样的 key-value 对。如Redis文档数据库 &#xff1a; NoSQL 与关系型数据的结合&#xff0c;最像关系…

免费电视TV盒子软件,好用的免费电视盒子软件大全,免费电视盒子APP大全,2024最新整理

1、TVbox下载地址、影视接口、配置教程 下载地址 TVbox TVbox可用接口地址合集 注&#xff1a;接口均来源于互联网收集分享&#xff01;所有接口都是经过测试的&#xff0c;如果出现加载失败等情况&#xff0c;可能是因为接口针对的盒子有兼容问题&#xff0c;可以多试试几…

Linux中查看端口被哪个进程占用、进程调用的配置文件、目录等

1.查看被占用的端口的进程&#xff0c;netstat/ss -antulp | grep :端口号 2.通过上面的命令就可以列出&#xff0c;这个端口被哪些应用程序所占用&#xff0c;然后找到对应的进程PID https://img-blog.csdnimg.cn/c375eb2bed754426b373907acaa7346e.png 3.根据PID查询进程。…

isctf---web

圣杯战争 php反序列 ?payloadO:6:"summon":2:{s:5:"Saber";O:8:"artifact":2:{s:10:"excalibuer";O:7:"prepare":1:{s:7:"release";O:5:"saber":1:{s:6:"weapon";s:52:"php://filter…

Ubuntu系统中部署C++环境与Visual Studio Code软件

本文介绍在Linux Ubuntu操作系统下,配置Visual Studio Code软件与C++代码开发环境的方法。 在文章VMware虚拟机部署Linux Ubuntu系统的方法中,我们介绍了Linux Ubuntu操作系统的下载、安装方法;本文则基于前述基础,继续介绍在Linux Ubuntu操作系统中配置Visual Studio Code…

【GitHub项目推荐--游戏模拟器(switch)】【转载】

01 任天堂模拟器 yuzu 是 GitHub 上斩获 Star 最多的开源 Nintendo Switch 模拟器 &#xff0c;使用 C 编写&#xff0c;考虑到了可移植性&#xff0c;该模拟器包括 Windows 和 Linux 端。 如果你的 PC 满足必要的硬件要求&#xff0c;该模拟器就能够运行大多数商业游戏&…

Django实战

一、开发登录表单 def login_form(request):html = <html><body><form method="post">用户名:<input name = "username" type="text"></input></br>密码:<input name = "password" type = &q…

CSS 之 图片九宫格变幻效果

一、简介 ​ 本篇博客用于讲解如何实现图片九宫格变幻的样式效果&#xff0c;将图片分为九块填充在33的的九宫格子元素中&#xff0c;并结合grid、hover、transition等CSS属性&#xff0c;实现元素hover时&#xff0c;九宫格子元素合并为一张完整图片的动画效果。 ​ 为了简化…

SpringMVC 环境搭建入门

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架&#xff0c;属于SpringFrameWork 的后续产品&#xff0c;已经融合在 Spring Web Flow 中。 SpringMVC 已经成为目前最主流的MVC框架之一&#xff0c;并且随着Spring3.0 的发布&#xff0c;全面…

02 Redis之配置文件

3. Redis配置文件 3.1 网络部分 首先明确&#xff0c;tcp-backlogestablished Linux 内核 2.2 版本之后&#xff08;现在大部分都是3.x了&#xff09; TCP 系统中维护了两个队列, 用来存放TCP连接 a. SYN_RECEIVED 队列中存放未完成三次握手的连接 b. ESTABLISHED队列中存放已…

Java面试题之序列化和反序列化

Java面试题之序列化和反序列化 文章目录 Java面试题之序列化和反序列化序列化和反序列化什么是序列化?什么是反序列化?如果有些字段不想进行序列化怎么办&#xff1f;常见序列化协议有哪些&#xff1f;为什么不推荐使用 JDK 自带的序列化&#xff1f; 文章来自Java Guide 用于…

腾讯云4核16G服务器价格,用于幻兽帕鲁Palworld专用

腾讯云幻兽帕鲁服务器4核16G、8核32G和16核64G配置可选&#xff0c;4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G35M配置580元年1个月、1740元3个月、6960元一年&#xff0c;腾讯云百科txybk.com分享腾讯云幻兽帕鲁…

腾讯云幻兽帕鲁专有服务器配置价格表,4核16G、8核32G

幻兽帕鲁服务器配置CPU内存多大合适&#xff1f;如何选择&#xff1f;最低4核8G起步&#xff0c;4核16G是官方推荐配置&#xff0c;最好是4核32G配置。阿腾云atengyun.com分享幻兽帕鲁Palworld服务器CPU内存配置及租用费用&#xff0c;如下图&#xff0c;Palworld官方推荐服务器…

特征点匹配 harris

算法的核心是利用局部窗口在图像上进行移动&#xff0c;判断灰度是否发生较大的变化。如果窗口内的灰度值&#xff08;在梯度图上&#xff09;都有较大的变化&#xff0c;那么这个窗口所在区域就存在角点。 这样就可以将 Harris 角点检测算法分为以下三步&#xff1a; 当窗口…