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 //www.master.com/index.php

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

壓測完成後,我們通過RedisDesktopManager(RDM)軟體來查看搶購結果,可以看到即使是200的並發量,最終也只有10個用戶搶購到商品,並且搶購成功的用戶被寫入到了secKill:11211:users的集合中,我們可以另外開一個守護進程專門用於從集合中獲取用戶ID處理後續事宜(將數據落盤寫入資料庫、給用戶發簡訊等)

使用Redis+Lua來解決搶購秒殺類問題是當前比較流行的一種做法,希望對正在開發秒殺搶購功能的你能產生幫助。