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

相关推荐
不是株2 分钟前
Redis(入门篇)
数据库·redis·缓存
2401_873204652 分钟前
Python面向对象编程(OOP)终极指南
jvm·数据库·python
0xDevNull9 分钟前
MySQL 三大日志系统深度解析:Binlog、Redo Log、Undo Log
数据库·mysql
逃逸线LOF12 分钟前
数据源 C3PO与Druid
数据库·oracle
m0_5698814725 分钟前
使用Python进行网络设备自动配置
jvm·数据库·python
Zzxy33 分钟前
HikariCP连接池
java·数据库
钰衡大师34 分钟前
MySQL 数据库备份方案
数据库·mysql
殷紫川38 分钟前
别等业务中断才补坑!RTO/RPO 核心逻辑与全场景灾备架构选型全攻略
数据库·架构
reasonsummer44 分钟前
【办公类-133-02】20260319_学区化展示PPT_02_python(图片合并文件夹、提取同名图片归类文件夹、图片编号、图片GIF)
前端·数据库·powerpoint
2401_831920741 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python