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

news2024/12/30 2:54:17

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]) then
            return 1
          else
            return 0
          end
        `;

        // 执行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] then
            return redis.call("DEL", KEYS[1])
          else
            return 0
          end
        `;
        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]) then
            return 1
          else
            return 0
          end
        `;

        // 执行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] then
            return redis.call("DEL", KEYS[1])
          else
            return 0
          end
        `;
        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.coloradmin.cn/o/1415681.html

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

相关文章

搞定App关键词和评论

从关键词优化的三大基本概念走起&#xff01; 关联性 优化师一般如何选择关联性高的关键词呢&#xff1f; 主要思路如下&#xff1a;品牌词-关联词-竞品词-竞品关键词&#xff0c;优先级从前到后依次降低&#xff0c;通过ASO优化工具筛选出合适的关键词。做ASO有一个好处就是…

vusui css 使用,简单明了 适合后端人员 已解决

vusui-cssopen in new window 免除开发者繁复的手写 CSS 样式&#xff0c;让 WEB 前端开发更简单、灵活、便捷&#xff01;如果喜欢就点个 ★Staropen in new window 吧。 移动设备优先&#xff1a; vusui-css 包含了贯穿于整个库的移动设备优先的样式。浏览器支持&#xff1a…

vue3使用最新的属性defineModel实现父子组件数据响应式绑定

子父之间使用v-model双向绑定数据&#xff0c;子组件每次都要写emit和props觉得麻烦&#xff1f;以前&#xff0c;为了使组件支持与v-model双向绑定&#xff0c;它需要&#xff08;1&#xff09;声明prop&#xff0c;&#xff08;2&#xff09;在打算更新prop时发出相应的updat…

用C语言实现贪吃蛇游戏!!!

前言 大家好呀&#xff0c;我是Humble&#xff0c;不知不觉在CSND分享自己学过的C语言知识已经有三个多月了&#xff0c;从开始的C语言常见语法概念说到C语言的数据结构今天用C语言实现贪吃蛇已经有30余篇博客的内容&#xff0c;也希望这些内容可以帮助到各位正在阅读的小伙伴…

Linux cat,tac,more,head,tail命令 查看文本

目录 一. cat 和 tac命令二. head 和 tail 命令三. more命令 一. cat 和 tac命令 cat&#xff1a;用来打开文本文件&#xff0c;从上到下的顺序显示文件内容。tac&#xff1a;用法和cat相同&#xff0c;只不过是从下到上逆序的方式显示文件内容。当文件的内容有很多的时候&…

canvas绘制旋转的大风车

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

vue中的Mutations

目录 一&#xff1a;介绍 二&#xff1a;例子 一&#xff1a;介绍 Vuex 中的 mutation 非常类似于事件&#xff1a; 每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的函数&#xff0c;并且它会接受 sta…

操作系统-线程的实现方式和多线程模型(用户级线程 内核级线程 多线程模型的情况)和线程的状态,转换,组织,控制

文章目录 线程的实现方式和多线程模型总览线程的实现方式用户级线程内核级线程多线程模型一对一多对一多对多 小结 线程的状态,转换,组织,控制总览 线程的状态与转换线程的组织与控制 线程的实现方式和多线程模型 总览 线程的实现方式 用户级线程 程序自己通过自己设计的线程…

03 Redis之命令(基本命令+Key命令+String型Value命令与应用场景)

Redis 根据命令所操作对象的不同&#xff0c;可以分为三大类&#xff1a;对 Redis 进行基础性操作的命令&#xff0c;对 Key 的操作命令&#xff0c;对 Value 的操作命令。 3.1 Redis 基本命令 一些可选项对大小写敏感, 所以应尽量将redis的所有命令大写输入 首先通过 redis-…

一行命令在 wsl-ubuntu 中使用 Docker 启动 Windows

在 wsl-ubuntu 中使用 Docker 启动 Windows 0. 背景1. 验证我的系统是否支持 KVM&#xff1f;2. 使用 Docker 启动 Windows3. 访问 Docker 启动的 Windows4. Docker Hub 地址5. Github 地址 0. 背景 我们可以在 Windows 系统使用安装 wsl-ubuntu&#xff0c;今天玩玩在 wsl-ub…

数据库查询3

目录 1. 多表查询 1.1.1 介绍 1.1.2 分类 1.2 内连接 1.3 外连接 1.4 子查询 1.4.1 介绍 1.4.2 标量子查询 1.4.3 列子查询 1.4.4 行子查询 1.4.5 表子查询 2. 事务 2.1 操作 2.2 四大特性 数据库总结2 数据库总结1 1. 多表查询 1.1.1 介绍 多表查询&#xff…

RLHF学习

整体流程 三个步骤分解&#xff1a; 预训练一个语言模型 (LM) &#xff1b;聚合问答数据并训练一个奖励模型 (Reward Model&#xff0c;RM) &#xff1b;用强化学习 (RL) 方式微调 LM。 RW RM 的训练是 RLHF 区别于旧范式的开端。这一模型接收一系列文本并返回一个标量奖励&…

1、PDManer 快速入门

文章目录 序言一、快速入门1.1 PDMan 介绍1.2 特点1.3 下载和安装 小结 序言 本人长期以来一直从事于应用软件的研发以及项目实施工作&#xff0c;经常做数据库建模&#xff08;数据表设计&#xff09;。有一款称心如意的数据库建模工具&#xff0c;自然能够事半功倍&#xff0…

【算法路线图】算法小抄题解-一文理解算法体系-费元星

做研发多年&#xff0c;对算法理解一直不够成体系&#xff0c;基本是每次在面试的时候才会去重点看算法&#xff0c;刷一些题&#xff0c;因此在这里&#xff0c;把我多年的总结发出来&#xff0c;希望晚辈站在一个高的位置学习。 最新链接&#xff1a;有道云笔记 -----------…

阿里云部署配置幻兽帕鲁Palworld联机服务器详细教程

阿里云作为国内领先的云计算服务提供商&#xff0c;为企业和个人提供了丰富的云服务。本文将为大家详细介绍如何在阿里云上配置幻兽帕鲁Palworld联机服务器&#xff0c;以便与更多玩家共同体验游戏的乐趣。 第一步&#xff1a;登录服务器创建页 1、进入幻兽帕鲁联机服务快速部…

设计模式⑩ :用类来实现

文章目录 一、前言二、Command 模式1. 介绍2.应用3. 总结 三、Interpreter 模式1. 介绍2. 应用3. 总结 参考文章 一、前言 有时候不想动脑子&#xff0c;就懒得看源码又不像浪费时间所以会看看书&#xff0c;但是又记不住&#xff0c;所以决定开始写"抄书"系列。本系…

GCP :Stackdriver Logging

官方介绍 Logs Explorer 利用 Logs Explorer&#xff0c;您可以通过灵活的查询语句、丰富的直方图视觉呈现、简单的字段浏览器以及保存查询的功能&#xff0c;对日志进行搜索、排序和分析。设置提醒以便在您包含的日志中出现特定消息时通知您&#xff0c;或者使用 Cloud Moni…

GPT-SoVITS 测试

开箱直用版&#xff08;使用 AutoDL&#xff09; step1 打开地址 https://www.codewithgpu.com/i/RVC-Boss/GPT-SoVITS/GPT-SoVITS-Official 选择 AutoDL创建实例&#xff0c;选择 3080ti 机器 step2 创建好实例之后&#xff0c;进入命令行&#xff0c;输入命令 echo {}>…

Kubernetes成本优化

云原生可以帮助团队更精细化利用资源&#xff0c;但如果缺乏工具的帮助&#xff0c;很难采取适当的措施优化资源的使用。本文介绍了若干用于可视化Kubernetes资源使用情况的工具&#xff0c;并且可以自定义策略优化资源使用&#xff0c;实现更好的成本优化。原文: Kubernetes C…

【计算机二级考试C语言】C强制类型转换

C 强制类型转换 强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型&#x…