逐步完善秒杀过程
场景:电商平台秒杀袜子,共计100双,每人限购1双;
普通代码 (在并发量很少时没问题)
import redis from '../modules/redis';
class DemoService{
static async miaosha(){
const redisName='mssp-001'
//await redis.set(redisName,100,'EX',-1) //向redis 写入数据
const count=await redis.get(redisName);
if(Number(count) <= 0) return {statusCode:1001,data:{},message:'error,Insufficient quantity'};//商品数量不足
const count1=await redis.decr(redisName);
console.log('count1=',count1);
return count1 > 0 ? {statusCode:1000,data:{},message:'成功'}:{statusCode:1001,data:{},message:'失败'}
}
}
export default DemoService;
上面的代码,如果在高并发下,当多个用户同一时刻都走过了
const count=await redis.get(redisName);
就会出现超卖
完善代码1,用setnx加锁
import redis from '../modules/redis';
class DemoService{
static async miaosha(){
const redisName='mssp-001'
const lockName='lock-001';
//await redis.set(redisName,100,'EX',-1) //向redis 写入数据
const addLock=await redis.setnx(lockName,'');//加锁成功返回true 已存在锁,枷锁失败都返回false
if(!addLock) return {statusCode:1001,data:{},message:'error,Please try again'};//解决多人
const count=await redis.get(redisName);
if(Number(count) <= 0) return {statusCode:1001,data:{},message:'error,Insufficient quantity'};//商品数量不足
const count1=await redis.decr(redisName);
console.log('count1=',count1);
redis.del(lockName);//秒杀成功,将所删除
return count1 > 0 ? {statusCode:1000,data:{},message:'成功'}:{statusCode:1001,data:{},message:'失败'}
}
}
export default DemoService;
上面代码,在正常情况下,是可以的,
如果,加完锁后,在扣减数量时出现网络异常等情况,锁就会一直存在,后面的用户就一直不能参与秒杀
完善代码2
通过try …catch 解决异常情况下锁的问题
import redis from '../modules/redis';
class DemoService{
static async miaosha(){
const redisName='mssp-001'
const lockName='lock-001';
//await redis.set(redisName,100,'EX',-1) //向redis 写入数据
const addLock=await redis.setnx(lockName,'');//加锁成功返回true 已存在锁,枷锁失败都返回false
if(!addLock) return {statusCode:1001,data:{},message:'error,Please try again'};//解决多人
const count=await redis.get(redisName);
if(Number(count) <= 0) return {statusCode:1001,data:{},message:'error,Insufficient quantity'};//商品数量不足
let count1=0;
try {
count1=await redis.decr(redisName);
} catch (_error:any) {
console.error(_error);
}finally{
redis.del(lockName);//秒杀成功,将所删除
}
console.log('count1=',count1);
return count1 > 0 ? {statusCode:1000,data:{},message:'成功'}:{statusCode:1001,data:{},message:'失败'}
}
}
export default DemoService;
上面的代码是不是看起来很完善了?
no! 如果删除锁的时候,出现一些问题,造成删除锁失败,怎么办
完善代码3 给锁加一个合理的过期时间
import redis from '../modules/redis';
class DemoService{
static async miaosha(){
const redisName='mssp-001'
const lockName='lock-001';
//await redis.set(redisName,100,'EX',-1) //向redis 写入数据
const addLock=await redis.setnx(lockName,'');//加锁成功返回true 已存在锁,枷锁失败都返回false
if(!addLock) return {statusCode:1001,data:{},message:'error,Please try again'};//解决多人
await redis.setex(lockName, 30,redisName);// 增加30秒过期处理
const count=await redis.get(redisName);
if(Number(count) <= 0) return {statusCode:1001,data:{},message:'error,Insufficient quantity'};//商品数量不足
let count1=0;
try {
count1=await redis.decr(redisName);
} catch (_error:any) {
console.error(_error);
}finally{
redis.del(lockName);//秒杀成功,将所删除
}
console.log('count1=',count1);
return count1 > 0 ? {statusCode:1000,data:{},message:'成功'}:{statusCode:1001,data:{},message:'失败'}
}
}
export default DemoService
写到这里,心里想的是不是这下肯定没问题了
但是 问题还是有的
如果用户A 这个过程超过了设置锁的30s 锁过期,此时B用户进入,A删锁后,C又可以进入,
同样会出现超卖
怎么办呢?
完善代码4
给setnx value 标记是哪个用户的,此处可用uuid、guid 能标明唯一用户就可以
解决方案一:
import redis from '../modules/redis';
import uuid from 'node-uuid';
class DemoService{
static async miaosha(){
const redisName='mssp-001'
const lockName='lock-001';
const uid=uuid.v4();
//await redis.set(redisName,100,'EX',-1) //向redis 写入数据
const addLock=await redis.setnx(lockName,'');//加锁成功返回true 已存在锁,枷锁失败都返回false
if(!addLock) return {statusCode:1001,data:{},message:'error,Please try again'};//解决多人
await redis.setex(lockName, 30, redisName+uid);// 增加30秒过期处理
const count=await redis.get(redisName);
if(Number(count) <= 0) return {statusCode:1001,data:{},message:'error,Insufficient quantity'};//商品数量不足
let count1=0;
try {
count1=await redis.decr(redisName);
} catch (_error:any) {
console.error(_error);
}finally{
const lockData=await redis.get(lockName);//
if(lockData === redisName+uid) redis.del(lockName);//秒杀成功,如果是当前用户添加的锁,将所删除,防止锁时间超时,出现超卖
}
console.log('count1=',count1);
return count1 > 0 ? {statusCode:1000,data:{},message:'成功'}:{statusCode:1001,data:{},message:'失败'}
}
}
export default DemoService;
解决方案二 : 在加锁成功后去开启个定时器,间隔10秒去更新一次锁的时间,将时间后延30s
import redis from '../modules/redis';
import uuid from 'node-uuid';
class DemoService{
static async miaosha(){
const redisName='mssp-001'
const lockName='lock-001';
const uid=uuid.v4();
let t:any=null
//await redis.set(redisName,100,'EX',-1) //向redis 写入数据
const addLock=await redis.setnx(lockName,'');//加锁成功返回true 已存在锁,枷锁失败都返回false
if(!addLock) return {statusCode:1001,data:{},message:'error,Please try again'};//解决多人
await redis.setex(lockName, 30, redisName+uid);// 增加30秒过期处理
//此处可添加定时器
t=setInterval(function name() {
redis.setex(lockName, 30, redisName+uid);// 增加30秒过期处理
},5000)
const count=await redis.get(redisName);
if(Number(count) <= 0) return {statusCode:1001,data:{},message:'error,Insufficient quantity'};//商品数量不足
let count1=0;
try {
count1=await redis.decr(redisName);
} catch (_error:any) {
console.error(_error);
}finally{
clearInterval(t);//清除定时器
const lockData=await redis.get(lockName);
if(lockData === redisName+uid) redis.del(lockName);//秒杀成功,如果是当前用户添加的锁,将所删除,防止锁时间超时,出现超卖
}
console.log('count1=',count1);
return count1 > 0 ? {statusCode:1000,data:{},message:'成功'}:{statusCode:1001,data:{},message:'失败'}
}
}
export default DemoService;
以为这就完善了?
no
如果出现服务器脱机怎么办?
如果redis是主从、哨兵、集群模式,一个节点挂了怎么?添加一个新的节点怎么办?
非代码层面的问题,后面文章在详解
压测 建议用jmeter, 被卸载了,就用工具 Apifox 测试吧
自动化测试 线程数 100,循环5次 将redis商品数量设置成10000
运行