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

相关推荐
oradh40 分钟前
Oracle TX 锁 Mode 4(Share)问题排查总结
数据库·oracle·tx 锁 mode 4·oracle tx 锁
yi.Ist1 小时前
数据定义语言-DDL操作
数据库·学习·mysql·oracle·大海豚
2601_963282773 小时前
工程项目、政企采购为什么优先选择对讲机批量采购?
数据库
Doraemomo3 小时前
SQLite数据库
数据库·sqlite
云贝教育-郑老师3 小时前
MySQL 的“黑匣子“:Performance Schema 把数据库内部变成一张可查的表
数据库·mysql
这个DBA有点耶5 小时前
同城双活落地的三座山:网络延迟、脑裂预防、反向同步
数据库·架构·dba
青春之我_XP5 小时前
MySQL 常用日期函数 实战指南
数据库·sql·mysql·数据分析·数据库开发·日期函数
Nturmoils5 小时前
sys_dump 备了库,角色和权限别漏在外面
数据库
接着奏乐接着舞。5 小时前
【2026】73道Redis 常见面试题与参考答案
数据库·redis·后端·缓存