NestJS+Redis实现分布式锁
高并发场景下容易出现的超卖问题(一张票卖给两个客户,或是库存卖成负数),一个常用的解决方法就是加锁。对于单机系统,内存级别的锁就足够应付(如c#中的lock);对于分布式系统Redis往往是一个常见的选项。当然,有一点要清楚的是:加锁有可能会影响代码执行效率,不是所有场景都适合加锁。
这里为了简化问题,假设有如下场景
请求到达后,代码会先查询是否还有库存,再创建订单,最后库存减一。 此处的并发问题是,如果在库存还剩一个的时候,两个请求同时到达第一步,此时都认为还有剩余,于是都创建了订单,最后库存减成了负数。
为了解决这个问题,以下示例在NestJS中使用Redis,为代码加锁,保证不会出现并发冲突。其逻辑是根据产品ID,在请求开始时,往redis中添加一条对应的记录,当请求处理结束,或是超时、异常时,删除该redis记录。其它请求进入时,也会查询redis中有无对应产品ID的记录,如果有就等待,直到解锁(记录被删除)或超时。
//新建nestjs项目,添加依赖
nest new redis-lock-demo
//这里是把redis作为数据库,记录产品剩余,非必须
npm install cache-manager
npm install -D @types/cache-manager
npm install --save cache-manager-redis-store
//以下两个依赖是实现了redis锁的插件
npm install nestjs-redis --save
npm install nestjs-simple-redis-lock
app.module.ts
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApiController } from './api/api.controller';
import * as redisStore from 'cache-manager-redis-store';
import { RedisModule } from 'nestjs-redis';
import { RedisLockModule } from 'nestjs-simple-redis-lock';
@Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
RedisModule.register({
host: 'localhost',
port: 6379,
}),
RedisLockModule.register({}),
],
controllers: [AppController, ApiController],
providers: [AppService],
})
export class AppModule {}
api.controller.ts
import { Controller, Get, CACHE_MANAGER, Inject } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { RedisLockService, RedisLock } from 'nestjs-simple-redis-lock';
@Controller('api')
export class ApiController {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache,
protected readonly lockService: RedisLockService, // inject RedisLockService
) {}
@Get('long')
/**
* Automatically unlock after 1 min
* Try again after 50ms if failed
* The max times to retry is 200
*/
@RedisLock('prod-001-lock', 1 * 60 * 1000, 50, 200)
async getLongService(): Promise<string> {
const balance = (await this.cacheManager.get('prod-001')) as number;
if (balance <= 0) {
return 'out of stock';
}
await new Promise((resolve) =>
setTimeout(() => {
console.log('do sonething long');
resolve(1);
}, 5000),
);
await this.cacheManager.set('prod-001', balance - 1, { ttl: 0 });
return 'order created';
}
}
api.controller.ts中的getLongService方法,先检查redis缓存中的产品剩余是否小于0,是则返回out of stock,负责等待5秒,库存减一,并返回order created。 如果不添加@RedisLock标签,两个客户端并发请求的话,会看到库存变为-1。 加了该标签则第一个请求处理的时候,第二个请求会等待。
当然,此处是在@RedisLock标签中hardcode了产品id,真实情况下需要动态指定,参见github地址。 代码示例如下:
await this.lockService.lock('test1', 2 * 60 * 1000, 50, 100);
this.lockService.unlock('test1');
最后给上完整代码示例。
原文地址:https://www.cnblogs.com/Andy1982/p/15191698.html
- Javascript数组
- 1588: [HNOI2002]营业额统计
- [git]撤销的相关命令:reset、revert、checkout
- Thrift教程初级篇——thrift安装环境变量配置第一个实例
- 1083: [SCOI2005]繁忙的都市
- 1015: [JSOI2008]星球大战starwar
- Tyvj P1813 [JSOI2008]海战训练
- 1820: [JSOI2010]Express Service 快递服务
- 3038: 上帝造题的七分钟2
- 1854: [Scoi2010]游戏
- Javascript字符串
- Codevs3278[NOIP2013]货车运输
- 关于使用lazytag的线段树两种查询方式的比较研究
- Java 持久化操作之 --XML
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- shiro会话管理示例代码
- Windows Apache2.4 VC9(ApacheHaus)详细安装配置教程
- 在centos 7中安装配置k8s集群的步骤详解
- Centos7.2 编译安装方式搭建 phpMyAdmin
- CentOS 6.5 web服务器apache的安装与基本设置
- Linux本机与服务器文件互传及Linux服务器文件上传下载命令写法
- linux利用read命令获取变量中的值
- 解决Centos7 安装腾达U12无线网卡驱动问题
- CentOS 6.5上编译安装Apache服务器的方法(最小化安装)
- 固定QPS压测模式探索
- Centos6 网络配置的实例详解
- centos6.5升级安装配置supervisor的教程
- Linux的路由表详细介绍
- Centos7.3下vsftp服务的安装方法
- 详解Linux 安装 JDK、Tomcat 和 MySQL(图文并茂)