1. 程式人生 > 其它 >nginx+lua樂觀鎖實現秒殺

nginx+lua樂觀鎖實現秒殺

package.path = '/class_goods/nginx/lua/5.1/lua-redis-cluster-master/?.lua;;/class_goods/nginx/lua/5.1/lua-resty-redis/lib/?.lua;;/class_goods/nginx/lua/5.1/lua-resty-limit-traffic-master/lib/?.lua;;'
ngx.header.content_type="text/plain";
--獲取get或post引數--------------------

local request_method = ngx.var.request_method
local args = nil
local param = nil
--獲取引數的值
--獲取秒殺下單的使用者id
if "GET" == request_method then
args = ngx.req.get_uri_args()
elseif "POST" == request_method then
ngx.req.read_body()
args = ngx.req.get_post_args()
end
user_id = args["user_id"]
--使用者身份判斷--省略
--使用者能否下單--省略

--關閉redis的函式--------------------

local function close_redis(redis_instance)
-- if not redis_instance then
-- return
-- end
-- local ok,err = redis_instance:close();
-- if not ok then
ngx.say("close redis error : ",err);
-- end
end

--引入cjson類--------------------
--local cjson = require "cjson"

--連線redis--------------------
--[[
local function redis_cluster_init ()
local redis_cluster = require "resty.redis_cluster"
local cluster_id = "redis_cluster"
local startup_nodes = {
{"192.168.142.142", 6320},
{"192.168.142.142", 6321},
{"192.168.142.142", 6322},
{"192.168.142.142", 6323},
{"192.168.142.142", 6324},
{"192.168.142.142", 6325}
}
local opt = {
timeout = 1000,--執行超時時間
keepalive_size = 50,--長連線數量
keepalive_duration = 60000 --長連線保持時間
}
rc = redis_cluster:new(cluster_id, startup_nodes, opt)
return rc
end
local redis_instance = redis_cluster_init();
]]

local redis = require("resty.redis");
--local redis = require "redis"
-- 建立一個redis物件例項。在失敗,返回nil和描述錯誤的字串的情況下
local redis_instance = redis:new();
--設定後續操作的超時(以毫秒為單位)保護,包括connect方法
redis_instance:set_timeout(1000)
--建立連線
local ip = '192.168.142.142'
local port = 6323
--嘗試連線到redis伺服器正在偵聽的遠端主機和埠
local ok,err = redis_instance:connect(ip,port)
if not ok then
ngx.say("connect redis error : ",err)
return close_redis(redis_instance);
end

-- local redis = require "redis"

-- 載入nginx—lua限流模組
local limit_req = require "resty.limit.req"
-- 這裡設定rate=50個請求/每秒,漏桶桶容量設定為1000個請求
-- 因為模組中控制粒度為毫秒級別,所以可以做到毫秒級別的平滑處理
local lim, err = limit_req.new("my_limit_req_store", 2, 10)
if not lim then
ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
return ngx.exit(501)
end

local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)

ngx.say("計算出來的延遲時間是:")
ngx.say(delay)

--if ( delay <0 or delay==nil ) then
--return ngx.exit(502)
--end

--先死這個值為-1, 就是先不限流, 先測試下面的樂觀鎖程式碼。
--delay = -1

-- 1000以外的就溢位,回絕掉,比如100000個人來搶購,那麼100000-1000的請求直接nginx回絕
if not delay then
if err == "rejected" then
return ngx.say("1000以外的就溢位")
-- return ngx.exit(502)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(502)
end

-- 計算出要等很久,比如要等10秒的, 也直接不要他等了。要買家直接回家吃飯去
if ( delay >10) then
ngx.say("搶購超時")
return
end

--先到redis裡面新增sku_num鍵(參與秒殺的該商品的數量)
--併到redis裡面新增watch_key鍵(用於做樂觀鎖之用)

local resp, err = redis_instance:get("{miaosha}sku_num")
local watch_key, err = redis_instance:get("{miaosha}watch_key")
resp = tonumber(resp)
ngx.say("sku_num:")
ngx.say(resp)
if (resp > 0) then
--ngx.say("搶購成功")
redis_instance:watch("{miaosha}watch_key");
ngx.sleep(1)
local ok, err = redis_instance:multi();
local sku_num = tonumber(resp) - 1;
redis_instance:set("{miaosha}sku_num",sku_num);
redis_instance:set("{miaosha}watch_key",watch_key+1);
ans,err = redis_instance:exec()

if (tostring(ans) == "userdata: NULL") then
    ngx.say("搶購失敗,慢一丁點")
    return
    
else
    ngx.say("搶購成功")
    return
end

else
ngx.say("搶購失敗,手慢了")
return
end

--下面這行程式碼是進入正式下單;
ngx.exec('/create_order'); --注意這行程式碼前面不能執行ngx.say()

--[[
--每個使用者限購1個,判斷使用者是否已經搶購過了的參考程式碼邏輯思路如下(具體過程略,前端快取中也有這個類似的判斷用於限制對後端的請求):

建一張用於儲存已經搶購成功了的使用者的redis雜湊表

搶購前判斷是否在該表中
local res, err = redis_instance:hmget("myhash", "user_id")
搶購成功則儲存到該表
local res, err = redis_instance:hmset("myhash", "user_id", "1")
--]]