redis练习系列-03事务与商品秒杀练习

Redis 的事务就是将多个命令序列化,按顺序执行,并且它们在执行的过程中,不会被其他客户端发来的命令请求所打断。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务,使用 MULTI命令。
  • 命令入队,正常的操作命令。
  • 执行事务, EXEC命令。

也可以在 EXEC 之前用 DISCARD 放弃这个事务。

另外需要注意的是,

  1. 如果入队过程中命令报错了,执行时会直接报错,所有的命令都不会执行。
  2. 如果入队过程中没有命令报错,而在执行过程中,某个命令执行失败,则不影响其他命令的执行。

下面练习一下 redis 处理秒杀商品的案例。

秒杀商品

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
python复制代码def handle_buy_product(user):
"""
抢购商品
"""
product_number = redis_client.get(PRODUCT_NUMBER_KEY)
# 未设置商品数目时,秒杀尚未开始
if product_number is None:
print("秒杀尚未开始")
return False
# 如果用户已经秒杀成功了
if redis_client.sismember(SUCCESS_USER_KEY, user):
print("你已秒杀成功,不能重复购买")
return False
# 如果库存不足,秒杀失败
if int(product_number) <= 0:
print("没有库存了")
return False
# 商品数量减1
redis_client.decr(PRODUCT_NUMBER_KEY)
# 记录秒杀成功的用户
redis_client.sadd(SUCCESS_USER_KEY, user)
return True

使用 locust 模拟高并发场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
python复制代码from locust import HttpUser, task


class BuyProductUser(HttpUser):
@task()
def buy(self):
self.client.post("/buy")


if __name__ == "__main__":
import os

os.system("locust -f locust_test.py --host=http://localhost:8000")

可以发现会出现超卖的情况,也就是商品库存被扣到负数。

使用事务解决

我们可以使用 pipeline,watch, multi, execute 来解决。其本质上是使用了乐观锁来处理并发问题。

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
python复制代码def handle_buy_product(user):
"""
抢购商品
"""
pipe = redis_client.pipeline()
try:
# 增加监视
pipe.watch(PRODUCT_NUMBER_KEY)
# 获取库存
product_number = pipe.get(PRODUCT_NUMBER_KEY)
# 未设置商品数目时,秒杀尚未开始
if product_number is None:
print("秒杀尚未开始")
return False
# 如果用户已经秒杀成功了
if redis_client.sismember(SUCCESS_USER_KEY, user):
print("你已秒杀成功,不能重复购买")
return False
# 如果库存不足,秒杀失败
if int(product_number) <= 0:
print("没有库存了")
return False
# 事务
pipe.multi()
# 商品数量减1
pipe.decr(PRODUCT_NUMBER_KEY)
# 记录秒杀成功的用户
pipe.sadd(SUCCESS_USER_KEY, user)
# 执行
pipe.execute()
except WatchError:
print("秒杀失败")
return False
finally:
pipe.reset()
return True

实验结果可以发现,不会出现超卖问题了,但是,可以观察到,会出现很多次的秒杀失败,商品数量下降的也比较慢。

使用 lua 锁

我们可以使用 lua 锁(悲观锁)来解决上面的问题。

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
python复制代码def handle_buy_product(user):
"""
抢购商品
"""
try:
with redis_client.lock("my-lock-key", blocking_timeout=5) as lock:
# 获取库存
product_number = redis_client.get(PRODUCT_NUMBER_KEY)
# 未设置商品数目时,秒杀尚未开始
if product_number is None:
print("秒杀尚未开始")
return False
# 如果用户已经秒杀成功了
if redis_client.sismember(SUCCESS_USER_KEY, user):
print("你已秒杀成功,不能重复购买")
return False
# 如果库存不足,秒杀失败
if int(product_number) <= 0:
print("没有库存了")
return False
# 商品数量减1
redis_client.decr(PRODUCT_NUMBER_KEY)
# 记录秒杀成功的用户
redis_client.sadd(SUCCESS_USER_KEY, user)
return True
except LockError:
print("秒杀失败")
return False

代码可见:github.com/luxu1220/re…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%