《緩存利器》二、Lua模塊下的共享內存

上一節講到了worker進程的共享內存,它利用豐富的指令使數據的緩存操作變得非常簡單,但它也存在一些缺點。

1.worker進程之間會有鎖競爭,在高併發的情況下會增加性能開銷。
2.只支持Lua布爾值、數字、字符串和nil類型的數據,無法支持table類型的數據。
3.在讀取數據時有反序列化操作,會增加CPU開銷。

共享內存在Ngx_Lua中作爲緩存工具還是非常出色的。筆者在生產環境中,曾多次使用lua_shared_dict的各種特性,並未感受到存在明顯的性能問題。但如果讀者還是介意這些缺點或需要緩存更復雜的數據的話,可以使用lua-resty-lrucache。

10.2.1 安裝lua-resty-lrucache

lua-resty-lrucache是基於Ngx_Lua的緩存利器,它擁有如下優點。

1.支持更豐富的數據類型,可以把table存放在value中,這對數據結構複雜的業務非常有用。

2.可以預先分配key的數量,不用設置固定的內存空間,在內存的使用上更爲靈活。

3.每個worker進程獨立緩存,所以當worker進程同時讀取同一個key 時不存在鎖競爭。

但它與lua_shared_dict相比也有一些缺點。

1.因爲數據不在worker之間共享,所以無法保證在更新數據時,數據在同一時間的不同worker進程上完全一致。

2.雖然可以支持複雜的數據結構,但可使用的指令卻很少,如不支持消息隊列功能。

3.重載Nginx配置時,緩存數據會丟失。如果使用lua_shared_dict,則不會如此。

有利就有弊,讀者在使用時可以根據自身需求進行選擇。lua-resty-lrucache的安裝方式和其他的lua-resty模塊一樣,如下所示:

# git clone https://github.com/openresty/lua-resty-lrucache.git
# cp -r lua-resty-lrucache/lib/resty/lrucache* \
 /usr/local/nginx_1.12.2/conf/lua_modules/resty/

10.2.2 使用lua-resty-lrucache進行緩存的方法

通過下面的例子來了解一下lua-resty-lrucache的使用方式,首先需要對模塊進行加載,方法如下:
local lrucache = require "resty.lrucache"
local lrucache = require "resty.lrucache.pureffi"
讀者在加載lua-resty-lrucache時,需要把上面的2個文件複製到lua_package_path所設置的路徑上。它們的作用是一樣的,但性能有所區別:resty.lrucache適合用來緩存命中率高或讀操作遠遠大於寫操作的緩存業務;resty.lrucache.pureffi適合用來緩存命中率低或需要對key進行頻繁增、刪操作的緩存業務。請根據業務需求進行選擇。
然後,將下面的代碼寫入test_m.lua中,並將此文件放到lua_package_path的路徑下,代碼如下:

local _M = {}
local lrucache = require "resty.lrucache"
--在緩存上聲明1個1000個key的列表
local cache, err = lrucache.new(1000)
if not cache then
    return error("failed to create the cache: " .. (err or "unknown"))
end
--此函數用來往緩存中存儲key/value的值
local function mem_set()
    --set()中的內容從左到右順序依次是key、value、有效期(2s)
    cache:set("a", 19, 2)
    cache:set("b", {"1","2","3"},0.001)  --支持插入table類型的數據
    return
end
--此函數用來獲取緩存裏的value。 a即value的值,如果a爲nil,則表示value不存在或已過期;如果stale_data有值,也說明value已過期
local function mem_get(key)
    local a,stale_data = cache:get(key)
    return a,stale_data
end
function _M. fromcache ()
    --獲取a的值
local a,stale_data  =  mem_get("a")
    --如果a存在,就輸出a的值
    if a then
       ngx.say("a: ", a)
    --如果a不存在且stale_data有值,就輸出過期的value,並重新執行存儲操作,然後再次輸出value
    elseif stale_data then
       ngx.say("a 已經過期: " , stale_data)
       mem_set()
       local a_again  =  mem_get("a")
       ngx.say("a: ", a_again )
    --如果a 和 stale_data都不存在,則執行存儲操作後再輸出value
    else
       ngx.say("no found a")
       mem_set()
       local a_again  =  mem_get("a")
       ngx.say("a: ", a_again )

    end
end
return _M
修改nginx.conf文件,代碼如下:
location / {
    content_by_lua_block {
        --加載模塊,執行數據的讀取操作
        require("test_m").fromcache()
    }
}

重載Nginx配置,執行結果如下:

# curl   'http://testnginx.com/'
no found a
a: 19
[root@testnginx ~]# curl   'http://testnginx.com/'
a: 19
[root@testnginx ~]# curl   'http://testnginx.com/'
a: 19
[root@testnginx ~]# curl   'http://testnginx.com/'
a 已經過期: 19
a: 19

從執行結果可以看出:
1.第1次請求,因爲a沒有值,所以先輸出“no found a”,然後又執行了存儲操作。

2.第2次請求,因爲有緩存值,直接輸出value。

3.第3次請求,仍然有緩存值,直接輸出value。

4.第4次請求,因爲爲緩存數據設置的有效期很短,此時已經過期,所以輸出了過期的value,並再次執行存儲操作,又輸出了value。

如果嘗試重載Nginx配置,會發現每次重啓(restart)後a都沒有值,因爲在重載配置的過程中,緩存數據會丟失。
下面將對lua-resty-lrucache的常見指令進行說明。


new
語法:cache, err = lrucache.new(max_items [, load_factor])
含義:創建1個緩存實例。如果創建失敗會返回nil,並將錯誤信息返回給err。
max_items用來聲明緩存key的數量,從這個設置可以看出它雖然沒有規定內存的使用大小,但規定了key的數量。
load_factor參數是加載resty.lrucache.pureffi模塊時纔會用到的,它基於FFI(Foreign Function Interface,外部功能接口)的hash表的負載因子,值的區間在0.1~1之間,默認值是0.5。負載因子與hash數據的讀取時間和對內存空間大小的權衡有關,有興趣的讀者可以自行查詢相關信息。


set
語法:cache:set(key, value, ttl)
含義:把key/value存儲到緩存中。ttl是緩存的有效期,以秒爲單位,默認值是0,表示不會過期;支持設置爲0.001s。


get
語法:data, stale_data = cache:get(key)
含義:獲取指定key的值,如果key不存在或已過期,就返回nil;如果存在過期數據,過期的值會賦值給stale_data。


delete
語法:cache:delete(key)
含義:從緩存中移除指定的key。


flush_all
語法:cache:flush_all(key)
含義:刷新整個緩存區域的數據,等於清空內存中的數據。這種方式比創建新的緩存實例要快得多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章