在做类似抢购活动的程序,我们通常会使用“乐观锁”思路,既保证库存不会超卖,也能够应付大并发的情况,Redis 中 watch 就可以实现“乐观锁”。



Redis 中的事务 multi/exec 可以保证数据的原子性,但不能像 Mysql 那样在事务中出错回滚数据,而用 watch 来监听数据,就可以实现类似的功能。

先用 watch 监听后,在接下来事务的过程,如果数据被修改,则在执行 exec 时,则会报错,我们可以根据业务选择重试和返回结果。

Python 中通过管道 Pipeline 来实现 Redis 事务相关操作。

初始化线程池

1
2
3
4
5
6
import redis

# 创建连接池
pool = redis.ConnectionPool(host = '127.0.0.1', port=6379, db=0)
# 初始化 redis
r = redis.Redis(connection_pool = pool)

初始化管道

1
pipe = r.pipeline()

使用事务

1
2
3
4
5
6
7
8
9
10
11
KEY = 'count'
try:
pipe.watch(KEY) # 监听库存
pipe.multi() # 开始事务
pipe.set(KEY, 2) # 执行操作
pipe.execute() # 执行事务
except Exception as e:
# 事务执行过程中,如果数据被修改,则抛出异常,程序可以选择重试或退出
pass
finally:
pipe.reset() # 重置管道,为重试做准备

通过这些操作,我们可以先实现一个防止商品超卖的 demo。

代码位置 watch_demo.py

运行

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
$ python watch_demo.py
用户 0 抢购成功,商品剩余 9
用户 1 抢购成功,商品剩余 8
用户 2 抢购失败,重试一次
用户 3 抢购成功,商品剩余 7
用户 5 抢购失败,重试一次
用户 4 抢购失败,重试一次
用户 2 抢购失败,重试一次
用户 6 抢购失败,重试一次
用户 7 抢购失败,重试一次
用户 9 抢购失败,重试一次
用户 8 抢购成功,商品剩余 6
用户 10 抢购成功,商品剩余 5
用户 5 抢购成功,商品剩余 4
用户 12 抢购失败,重试一次
用户 6 抢购失败,重试一次
用户 13 抢购成功,商品剩余 3
用户 11 抢购失败,重试一次
用户 14 抢购失败,重试一次
用户 2 抢购失败,重试一次
用户 4 抢购失败,重试一次
用户 9 抢购成功,商品剩余 2
用户 7 抢购失败,重试一次
用户 12 抢购成功,商品剩余 1
用户 2 抢购停止,商品卖完
用户 6 抢购失败,重试一次
用户 14 抢购失败,重试一次
用户 4 抢购停止,商品卖完
用户 11 抢购成功,商品剩余 0
用户 7 抢购停止,商品卖完
用户 6 抢购停止,商品卖完
用户 14 抢购停止,商品卖完
03-16 19:58