redis防止超售
方式一
redis在应用中越来越广泛, 其中常用的大部分操作都是非原子性的, 例如set
/get
/hmset
…这些会在多个操作时后边的覆写前边的, 例如
1 | graph LR |
请求前后先后进入, 几乎同时抵达数据库, 当A
检查时没有请求, B
先后到达,也检查到没有请求, 此时向redis
写入当前请求信息, A写入,B也写入了, 且会被后写入的覆盖, 无提示, 此时便达不到限制的目的, 而且如果秒杀场景下请求大的情况下, 一下进入的可能销售远远超过库存的数量.
此时就需要使用redis的原子性操作, 同时只能一个读写, 写入/读取有失败提示.setnx
/getset
setnx Key Value
设置一个键值, 如果同时写入只有一个会成功返回 1, 其余失败返回 0, 满足了并发加锁限制,getset Key NewValue
获取并设置一个值, 成功会返回当前设置的值,
如果锁已超时,那么锁可能已由其他进程获得,这时直接执行 del Key
操作会导致把其他进程已获得 的锁释放掉
1 | // 函数封装 |
方式二
使用原子增减操作 incrby/decrby
, 下单时对数据进行增减, 优势无超长时间等待
如下: 简略代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38const http = require('http');
const Redis = require('ioredis');
const redis = new Redis();
http.createServer(async (req, res) => {
let key = 'test:count';
let count = 0;
switch (req.url) {
case '/count':
// 查询redis库存剩余 => 实际查询数据库库存剩余即可
count = await redis.get(key);
console.log(count)
break;
case '/set_count':
// 设置库存数
await redis.set(key, 10);
break;
default: // 抢购逻辑
let stock = await redis.get(key);
// 1. 查询当前库存, 如果有再进行
if (stock <= 0) {
res.statusCode = 500;
break;
}
// 模拟下单数量不同
let num = parseInt(Math.random() * 3 + 1)
// 2. 原子减库存, 如果原子减后小于0, 则返还减量
count = await redis.decrby(key, num);
if (count < 0) {
// 2.1 返还库存
await redis.incrby(key, num);
break;
}
// 3. 可以购买
console.log('库存: %d, 购买: %d', stock, num);
}
res.end();
}).listen(3000)
此案例使用 单机redis
/ pm2 start app.js -i 4
/ ab -c 10 -n 100
多进程进行/多请求模拟操作.
示例如下:
多进程启动
1
$> pm2 start app.js -i 4
ab
工具模拟请求1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43$> ab -c 10 -n 100 http://127.0.0.1:3000/buy
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient).....done
Server Software:
Server Hostname: 127.0.0.1
Server Port: 3000
Document Path: /buy/
Document Length: 0 bytes
Concurrency Level: 10
Time taken for tests: 0.031 seconds
Complete requests: 100
Failed requests: 0
Non-2xx responses: 94
Total transferred: 9286 bytes
HTML transferred: 0 bytes
Requests per second: 3180.16 [#/sec] (mean)
Time per request: 3.144 [ms] (mean)
Time per request: 0.314 [ms] (mean, across all concurrent requests)
Transfer rate: 288.39 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 1
Processing: 1 2 1.4 2 8
Waiting: 1 2 1.2 1 6
Total: 1 3 1.4 2 8
Percentage of the requests served within a certain time (ms)
50% 2
66% 2
75% 3
80% 3
90% 5
95% 6
98% 8
99% 8
100% 8 (longest request)pm2 log app
日志打印1
2
3
4
5
6
7$> pm2 log app
...
2|app | 库存: 10, 购买: 3
2|app | 库存: 7, 购买: 3
0|app | 库存: 7, 购买: 1
0|app | 库存: 7, 购买: 1
0|app | 库存: 7, 购买: 2
通过日志打印可以看出, 库存为7的时候, 有4个请求进入, 并且下单成功, 之后便没有了, 保证了库存安全
- 查看剩余库存
1
2$> curl http://localhost:3000/count
0
本文作者 : 萧逸雨
原文链接 : http://qiubo.ink/2020/12/25/redis防止超售/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!