10

Redis+Lua解决高并发场景抢购秒杀问题

 3 years ago
source link: https://www.cnblogs.com/itbsl/p/15021263.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Redis+Lua解决高并发场景抢购秒杀问题

之前写了一篇PHP+Redis链表解决高并发下商品超卖问题,今天介绍一些如何使用PHP+Redis+Lua解决高并发下商品超卖问题。

为何要使用Lua脚本解决商品超卖的问题呢?

  • Redis在2.6版本后原生支持Lua脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。
  • 将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。
  • 原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
  • 复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

首先,编写lua脚本,脚本名为secKill.lua:

-- 接收参数
local user_id  = KEYS[1]
local goods_id = KEYS[2]

-- 拼接字符串
local stock_key = "secKill:"..goods_id..":stock" -- 秒杀商品库存key
local users_key = "secKill:"..goods_id..":users" -- 成功秒杀商品的用户集合key

-- 判断用户是否已经成功秒杀过该商品,如果已经存在在集合中,说明已经成功秒杀该商品,直接返回标志2,防止重复抢购
local user_exists = redis.call('sismember', users_key, user_id)
if tonumber(user_exists, 10) == 1 then
    return 2
end

-- 获取当前商品库存,如果库存小于等于0,表名商品已经被抢购完了,否则库存-1,并将抢购成功的用户放入集合中
local left_goods_count = redis.call('get', stock_key)
if tonumber(left_goods_count, 10) <= 0 then
    return 0
else
    redis.call('decr', stock_key)
    redis.call('sadd', users_key, user_id)
end
return 1

上述代码中返回的数字0,1,2只是一种约定,自己可以根据自己的有业务约定不同状态返回的值。示例代码0:库存为0,1:秒杀成功,2:已秒杀成功的用户重复抢购。

lua脚本编写完成后,使用redis-cli命令生成该脚本的sha秘钥

redis-cli script load "$(cat /usr/local/redis/lua/secKill.lua)"
"63454a53284d9f6b30bdb6e5e12796a74f61f718"

最后,拿到lua脚本的sha秘钥,我们就可以在我们的代码中使用了。

$redis = new Redis();
$redis->connect("192.168.111.128", 6379);
$goodsId = 11211;
$userId = mt_rand(10000, 99999);
$res = $redis->evalSha('63454a53284d9f6b30bdb6e5e12796a74f61f718', [$userId, $goodsId], 2);

可以看到,我们将抢购逻辑写到lua脚本后,PHP代码就变得很少了,仅仅只有5行代码。

编写好代码,接着我们开始对上述代码进行测试。

首先,我们需要设置商品的库存量,正常逻辑是在后台商品管理页填写具体商品的库存量,此处假设我们的商品ID是11211(这个数字是不是很熟悉?是的,这是memcached的默认端口),商品数量为10个。

$redis-cli
> set secKill:11211:stock 10

我们使用ab压测工具模拟2000个用户并发量200来模拟抢购商品ID为11211的商品。

$ ab -n 2000 -c 200 http://www.master.com/index.php

如果没有ab工具需要使用 yum -y install httpd-tools安装

压测完成后,我们通过RedisDesktopManager(RDM)软件来查看抢购结果,可以看到即使是200的并发量,最终也只有10个用户抢购到商品,并且抢购成功的用户被写入到了secKill:11211:users的集合中,我们可以另外开一个守护进程专门用于从集合中获取用户ID处理后续事宜(将数据落盘写入数据库、给用户发短信等)

使用Redis+Lua来解决抢购秒杀类问题是当前比较流行的一种做法,希望对正在开发秒杀抢购功能的你能产生帮助。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK