以前寫了一篇PHP+Redis鏈表解決高併發下商品超賣問題,今天介紹一些如何使用PHP+Redis+Lua解決高併發下商品超賣問題。php
爲什麼要使用Lua腳本解決商品超賣的問題呢?html
首先,編寫lua腳本,腳本名爲secKill.lua:redis
-- 接收參數 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:已秒殺成功的用戶重複搶購。shell
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行代碼。memcached
編寫好代碼,接着咱們開始對上述代碼進行測試。高併發
首先,咱們須要設置商品的庫存量,正常邏輯是在後臺商品管理頁填寫具體商品的庫存量,此處假設咱們的商品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來解決搶購秒殺類問題是當前比較流行的一種作法,但願對正在開發秒殺搶購功能的你能產生幫助。