上一節講到了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)
含義:刷新整個緩存區域的數據,等於清空內存中的數據。這種方式比創建新的緩存實例要快得多。