接着上篇文章的理論原理,這裡用OpenResty來實作一下具體如何用OpenResty來處理接口資料的緩存處理。
我們先看下流程圖了解下具體的代碼邏輯,然後結合着看代碼更容易了解
流程圖
用到兩級緩存,一級緩存用于傳回最新的資料,二級緩存用來兜底資料,萬一後端業務服務出現問題,可以傳回兜底資料,防止用戶端出現空白或奔潰等情況,對使用者的影響降到最低;
通過鎖的方式來防止緩存失效時大量的并發請求都透傳到業務服務,使得服務壓力負載過高
local resty_lock, cjson = require "resty.lock", require "cjson"
local lock_name = "ngx_locks"
local primary_cache = ngx.shared.primary_cache -- 一級緩存,提高QPS
local secondary_cache = ngx.shared.secondary_cache -- 二級緩存,防資料托底
local config = {
rules = {
{uri = "/api/shop/detail"},
{uri = "/api/shop/list"}
},
dirty_params = {
"token", "mac"
}
}
-- 去除可變傳參
function rem_dirty(params)
for _, v in ipairs(config.dirty_params) do
if args and params[v] then
args[v] = nil
end
end
return args
end
-- 計算緩存鍵
function getKey(uri, params)
params = rem_dirty(params)
query = ngx.encode_args(params)
local request_uri = query == "" and uri or uri .. "?" .. query
return ngx.md5(request_uri)
end
function getData(key)
-- 從一級緩存取資料
local val = primary_cache:get(key)
if val then
return cjson.decode(val)
end
-- 建立鎖
local lock, err = resty_lock:new(lock_name, {timeout=2, exptime=5})
if not lock then
return nil, "failed to create lock: " .. err
end
-- -- 沒有擷取鎖
local elapsed, err = lock:lock(key)
if not elapsed then
local val, err = secondary_cache:get(key)
if val then
return cjson.decode(val)
end
return nil, err
end
-- 成功得到鎖!,從後端取
local val, err = fetchServer(self, key)
if not val then
local ok, err = lock:unlock()
if not ok then
return nil, "failed to unlock: " .. err
end
return nil, err
end
-- 用新擷取的值更新一級緩存
local ok, err = primary_cache:set(key, val, self.expire)
if not ok then
local ok, err = lock:unlock()
if not ok then
return nil, "failed to unlock: " .. err
end
return nil, "failed to update shm cache: " .. err
end
local ok, err = lock:unlock()
if not ok then
return nil, "failed to unlock: " .. err
end
return cjson.decode(val)
end
function fetchServer(key)
local http_params = ngx.req.get_uri_args()
value = ngx.location.capture(ngx.var.uri, {args=http_params})
-- 後端響應正常,更新托底資料
if value and value.status == ngx.HTTP_OK and value.body then
local body = cjson.decode(value.body)
if body.code and body.code == "0" then
value = cjson.encode(value)
local ok, err = secondary_cache:set(key, value, 86400) -- 有效期24個小時
if not ok then
return nil, 'failed to set secondary_cache' .. err
end
return value
end
end
-- 後端響應不正常,擷取托底資料
local val, err = secondary_cache:get(key)
if not val then
return nil, 'failed get value from secondary_cache'
end
return val
end
-- 檢測接口是否比對
function check(uri)
for _, v in ipairs(config.rules) do
if v.uri == uri then
return true;
end
end
return false
end
-- 入口
do
local params = ngx.req.get_uri_args()
local uri = ngx.var.uri
--判斷uri是否比對
local match = check(uri)
if match then
local cache_key = getKey(uri, params)
local val, err = getData(cache_key)
if not val then
if err then ngx.log(ngx.ERR, err) end
return
end
ngx.status = val.status
ngx.print(val.body)
return ngx.exit(val.status)
end
return
end