Lua脚本在秒杀场景下的使用

在秒杀这种高并发、大流量的场景下,利用数据库进行库存扣减、数据查询这种想法就不要考虑了。你要是有这种想法,劝你早点放弃,P0事故一定在等着你。之前用Lua脚本实现秒杀场景下的库存扣减感觉很不错,配合Redis秒杀效果杠杠的!Redis 2.6 版本通过内嵌支持 Lua 环境,会单线程原子性执行 Lua 脚本,保证 Lua 脚本在处理的过程中不会被任意其它请求打断。

先说一下大概的情况,在上秒杀的商品的时候,就直接将产品的库存、用户每人的限购的数量等信息直接用Hash数据类型保存在Redis的键goods_商品ID中,不设置缓存有效时间。用户的购买记录用zset保存purchase_goods_商品id中,用商品的ID作为key,用户唯一标识为member,购买的数量为score。相关的命令如下:

1
2
3
4
5
6
7
8
9
10
# 初始化一些秒杀产品
hmset seckill:goods:1000 goodsId 1000 name 戴森吸尘器V10Motorhead入门款,性价比之选 stock 10 purchaseLimit 1 price 999.99 startSaleTime 1614137304000 endSaleTime 1614140904000 coverImage https://dss2.bdstatic.com/8_V1bjqh_Q23odCf/pacific/1955446237.png

hmset seckill:goods:2000 goodsId 2000 name 戴森无叶风扇 stock 15 purchaseLimit 1 price 1999.99 startSaleTime 1640406504000 endSaleTime 1614140904000 coverImage https://dss2.bdstatic.com/8_V1bjqh_Q23odCf/pacific/1957729018.png

hmset seckill:goods:3000 goodsId 3000 name 戴森智能照明灯 stock 32 purchaseLimit 1 price 299.99 startSaleTime 1640406504000 endSaleTime 1614140904000 coverImage https://dss2.bdstatic.com/8_V1bjqh_Q23odCf/pacific/1947896752.jpg

hmset seckill:goods:4000 goodsId 4000 name 戴森AM10除菌加湿器 stock 15 purchaseLimit 1 price 1299.99 startSaleTime 1640752104000 endSaleTime 1610166504000 coverImage https://dss2.bdstatic.com/8_V1bjqh_Q23odCf/pacific/1947896752.jpg

hmset seckill:goods:5000 goodsId 5000 name 苹果AirPodsMax stock 13 purchaseLimit 1 price 2099.99 startSaleTime 1640752104000 endSaleTime 1610166504000 coverImage https://dss2.bdstatic.com/8_V1bjqh_Q23odCf/pacific/1983533493.jpg

具体的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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
--KEYS[1] 对应商品的购买记录:purchase_goods_1000
--KEYS[2] 商品的key:goods_1000
--ARGV[1] 购买人唯一标识memberId:2000
--ARGV[2] 购买数量number: 2

local goodsExists=redis.call('EXISTS', KEYS[2])
if goodsExists == 0
then
-- 产品不存在
return -1
end
local goodsStockExists=redis.call('hexists', KEYS[2], 'stock')
if goodsStockExists == 0
then
-- 代表产品库存不存在
return -2
end
--用户限购数量
local purchaseLimitExists=redis.call('hexists', KEYS[2], 'purchaseLimit')
if purchaseLimitExists == 0
then
-- 商品限购数量不存在
return -3
end
--用户限购数量
local purchaseLimit=tonumber(redis.call('hget', KEYS[2], 'purchaseLimit'))
--用户已经购买的数量
local purchasedNumber=redis.call('ZSCORE', KEYS[1], ARGV[1])
--购买数量不存在则默认为0
if not purchasedNumber
then
purchasedNumber = 0
end
-- 用户现在要购买的数量
local nowPurchasedNumber = tonumber(ARGV[2])
-- 用户将要购买的总数量
local totalPurchaseNumber = nowPurchasedNumber+purchasedNumber
if purchaseLimit < totalPurchaseNumber
then
-- 用户购买数量超过了限购数量
return -4
end
local stock=tonumber(redis.call('hget', KEYS[2], 'stock'))
if stock <= 0
then
-- 库存为0
return 1
end
if stock < nowPurchasedNumber
then
-- 库存不足
return 2
end

--扣减商品库存
redis.call('hincrby', KEYS[2], 'stock', -nowPurchasedNumber)
-- 更新用户的总购买数量
redis.call('zadd',KEYS[1], totalPurchaseNumber, ARGV[1])
-- 用户购买成功
return 0
  1. 尽管Lua会提供字符串和数字的自动转换,但是更好还是转换一下。tonumber就是把字符串数字转换成数字的函数。
  2. redis.call是用来执行redis函数的方法。
  3. 上述的脚本可以通过EVAL、EVALSHA或者redis-cli 参数的形式执行。但是上面的脚本已经格式化了,最好还是以后面的方式运行。
  4. Lua的注释是以 – 开头作为单行注释

具体执行演示如下:

1
2
3
Sam-Mac:Downloads Sam$ rediscli --eval  /Users/sam/Downloads/seckill.lua  purchase_goods_3000 seckill:goods:1000 , 202 1
(integer) 0
Sam-Mac:Downloads Sam$
  • –eval参数是告诉rediscli(这个是自定义redis-cli命令的地址)读取并运行后面的Lua脚本。

  • /Users/sam/Downloads/seckill.lua 是脚本的位置,后面跟着是传给Lua脚本的参数。

  • 逗号”,”前的purchase_goods_3000 seckill:goods:1000 是要操作的键,可以在脚本中用KEYS[1]、KEYS[2]获取,逗号”,”后面的202和1是参数,在脚本中能够使用ARGV[1]和ARGV[2]获得。

注意”,”逗号两边的空格不能省略,否则会出错

在Redis中调用Lua脚本,也是可以调试的。Redis 从 v3.2.0 开始支持 Lua debugger,可以加断点、print 变量信息、展示正在执行的代码。可以在命令中添加–ldb进行调试,这个参数是开启 Lua dubegger 的意思,这个模式下Redis会 fork 一个进程进入隔离环境,不会影响Redis正常提供服务,但调试期间,原始 redis 执行命令、脚本的结果也不会体现到 fork 之后的隔离环境之中。因此呢,还有另外一种调试模式 –ldb-sync-mode,也就是前面提到的同步模式,这个模式下,会阻塞 redis 上所有的命令、脚本,直到脚本退出,完全模拟了正式环境使用时候的情况,使用的时候务必注意这点。

添加和删除断点很简单,只需使用b 1 2 3 4在第1,2,3,4行添加b 0断点。该命令将删除所有断点。可以使用as参数删除选定的断点,即我们要删除的断点所在的行,但前缀为减号。例如b -3,从第3行中删除断点。

1
Sam-Mac:Downloads Sam$ rediscli --ldb --eval  /Users/sam/Downloads/seckill.lua  purchase_goods_3000 seckill:goods:1000 , 202 1

调试Lua脚本

上面就是今日文章的所以内容,这样看起来是不是觉得Lua脚本也是挺容易的吧!

  • 作者: Sam
  • 发布时间: 2020-12-24 22:40:10
  • 最后更新: 2021-01-06 21:07:07
  • 文章链接: https://ydstudios.gitee.io/post/d958d490.html
  • 版权声明: 本网所有文章除特别声明外, 禁止未经授权转载,违者依法追究相关法律责任!