在 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 即可模拟抢锁的场景

相关推荐
流星白龙5 小时前
【Redis】2.Redis重大版本
数据库·redis·junit
流星白龙5 小时前
【Redis】7.Hash表
数据库·redis·哈希算法
流星白龙6 小时前
【Redis】4.基本全局命令
数据库·redis·缓存
王八八。6 小时前
Navicat 17破解版下载安装教程 附安装激活步骤(2026 最新版)
数据库·navicat
Jelena157795857927 小时前
电商运营分析数据比价接口实战:多平台价格监控与智能决策系统
java·大数据·数据库
星栈7 小时前
从装一堆工具到看懂 Node 工程化思维:我的项目复盘记录
后端·node.js
流星白龙9 小时前
【Redis】3.Redis安装与命令行客户端
数据库·redis·缓存
凌虚10 小时前
基于 PostgreSQL WAL 构建 CDC 系统:原理与工程实现
数据库·后端·postgresql
BullSmall10 小时前
PostgreSQL 14 pg_dumpall 完整备份指南
数据库·postgresql·oracle
东方小月10 小时前
从零开发一个Coding Agent:monorepo项目搭建
前端·后端·node.js