在 EggJS 中实现 Redis 上锁

配置环境

下载 Redis

Windows

访问 https://github.com/microsoftarchive/redis/releases 选择版本进行下载 - 勾选 配置到环境变量 - 无脑下一步并安装

命令行执行:redis-cli -v 查看已安装的 Redis 版本,能成功查看就表示安装成功啦~

Mac

shell 复制代码
brew install redis # 安装 redis
brew services start redis # 启动 redis
brew services stop redis # 停止 redis
brew services restart redis # 重启 redis

启动 Redis

打开任务管理器,找到 Redis 服务,点击启动即可

配置 EggJS 项目

  1. 安装依赖
bash 复制代码
pnpm i egg-redis
  1. 配置插件
js 复制代码
// config/plugin.js
exports.redis = {
    enable: true,
    package: 'egg-redis',
};
js 复制代码
// config/config.default.js
exports.redis = {
    client: {
        port: 6379, // Redis port
        host: '127.0.0.1', // Redis host
        password: '',
        db: 0,
    },
};
  1. 扩展 helper
js 复制代码
// app/extend/helper.js
module.exports = {
    // 生成 redis 锁的控制器; val 为随机数, 防止解锁时误删其他请求的锁
    redisLockController(key, val = Math.random(), ttl = 5 * 60) {
        const app = this.app;
        return {
            // 上锁
            async lock() {
                // 使用 set 命令上锁并设置过期时间, 保证原子性
                const lockResult = await app.redis.set(
                    key,
                    val,
                    'EX',
                    ttl,
                    'NX'
                );
                return lockResult === 'OK';
            },
            // 解锁
            async unlock() {
                // 使用 lua 脚本校验锁并解锁, 保证原子性
                const script = `
					if redis.call('get', KEYS[1]) == ARGV[1] then
						return redis.call('del', KEYS[1])
					else
						return 0
					end
				`;
                // 使用 eval 命令执行 lua 脚本
                const unlockResult = await app.redis.eval(script, 1, key, val);
                return unlockResult === 1;
            },
        };
    },
};
  1. 使用 redis 上锁
js 复制代码
// app/controller/home.js
const { Controller } = require('egg');

module.exports = class HomeController extends Controller {
    async index() {
        const { id } = this.ctx.query;
        const result = await this.service.home.index(id);
        this.ctx.body = result;
    }
};
js 复制代码
// app/service/home.js
const { Service } = require('egg');

module.exports = class HomeService extends Service {
    async index(id = 0) {
        // 从 header 中获取 region 参数
        const region = this.ctx.get('region') || 'default';
        // 生成锁的 key
        const lockKey = `lock:${region}:${id}`;
        // 获取锁的控制器
        const { lock, unlock } = this.ctx.helper.redisLockController(lockKey);
        // 上锁
        const lockResult = await lock();
        // 上锁失败
        if (!lockResult) return { code: 500, msg: 'lock failed' };
        // 上锁成功, 执行业务逻辑
        let result;
        try {
            result = await this.mockSql(id);
        } catch (err) {
            result = { code: 500, msg: err.message };
        }
        // 解锁
        await unlock();
        // 返回结果
        return result;
    }

    // 模拟数据库查询
    async mockSql(id) {
        // 2s 后返回结果
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve({
                    code: 200,
                    msg: 'success',
                    data: { id, desc: 'egg is very good', time: Date.now() },
                });
            }, 2000);
        });
    }
};

模拟抢锁

开两个浏览器访问 http://localhost:7001 即可模拟抢锁的场景

相关推荐
前端 贾公子3 分钟前
Claude Code 的 skills 源码解析 (上)
数据库·人工智能
吠品6 分钟前
.NET 8 单文件发布:把 exe 和一堆 dll 打进一个文件里
服务器·数据库·windows
odoo中国10 分钟前
Odoo19 内置缓存原理、用法与避坑指南
缓存·odoo19·实际案例·缓存原理·内置缓存用法
cmes_love17 分钟前
期货五档tick数据下载教程期权五档高频历史数据以及分钟量化回测下载
数据库
蚂蚁数据AntData20 分钟前
从ChatBI到业务记忆:重新定义数据智能的生产力边界
大数据·网络·数据库·人工智能·算法
Quincy_Freak24 分钟前
技术随笔|银河麒麟aarch64内网轻量数据分析工具 SQLiteGo 使用体验
大数据·数据库·数据挖掘·数据分析·sqlite·银河麒麟·aarch64
Amnesia0_025 分钟前
MySQL视图和用户管理
数据库·mysql
矩阵科学29 分钟前
Langchain.js 实战五:Agent 实战
langchain·node.js
matrixmind135 分钟前
aiomysql:异步场景下的 MySQL 驱动
android·数据库·mysql·其他
数据库小学妹37 分钟前
时序数据怎么存?InfluxDB、TDengine、TimescaleDB与国产融合方案选型实战
大数据·数据库·经验分享·时序数据库·dba