天天看點

基于OpenResty動态接口緩存處理代碼實作

作者:slyvas

接着上篇文章的理論原理,這裡用OpenResty來實作一下具體如何用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           

繼續閱讀