緩存是一個(gè)大型系統(tǒng)中非常重要的一個(gè)組成部分。在硬件層面,大部分的計(jì)算機(jī)硬件都會(huì)用緩存來提高速度,比如 CPU 會(huì)有多級(jí)緩存、RAID 卡也有讀寫緩存。在軟件層面,我們用的數(shù)據(jù)庫就是一個(gè)緩存設(shè)計(jì)非常好的例子,在 SQL 語句的優(yōu)化、索引設(shè)計(jì)、磁盤讀寫的各個(gè)地方,都有緩存,建議大家在設(shè)計(jì)自己的緩存之前,先去了解下 MySQL 里面的各種緩存機(jī)制,感興趣的可以去看下High Performance MySQL這本非常有價(jià)值的書。
一個(gè)生產(chǎn)環(huán)境的緩存系統(tǒng),需要根據(jù)自己的業(yè)務(wù)場(chǎng)景和系統(tǒng)瓶頸,來找出最好的方案,這是一門平衡的藝術(shù)。
一般來說,緩存有兩個(gè)原則。一是越靠近用戶的請(qǐng)求越好,比如能用本地緩存的就不要發(fā)送 HTTP 請(qǐng)求,能用 CDN 緩存的就不要打到 Web 服務(wù)器,能用 Nginx 緩存的就不要用數(shù)據(jù)庫的緩存;二是盡量使用本進(jìn)程和本機(jī)的緩存解決,因?yàn)榭缌诉M(jìn)程和機(jī)器甚至機(jī)房,緩存的網(wǎng)絡(luò)開銷就會(huì)非常大,在高并發(fā)的時(shí)候會(huì)非常明顯。
我們介紹下在 OpenResty 里面,有哪些緩存的方法。
我們看下面這段代碼:
function get_from_cache(key)
local cache_ngx = ngx.shared.my_cache
local value = cache_ngx:get(key)
return value
end
function set_to_cache(key, value, exptime)
if not exptime then
exptime = 0
end
local cache_ngx = ngx.shared.my_cache
local succ, err, forcible = cache_ngx:set(key, value, exptime)
return succ
end
這里面用的就是 ngx shared dict cache。你可能會(huì)奇怪,ngx.shared.my_cache 是從哪里冒出來的?沒錯(cuò),少貼了 nginx.conf 里面的修改:
lua_shared_dict my_cache 128m;
如同它的名字一樣,這個(gè) cache 是 Nginx 所有 worker 之間共享的,內(nèi)部使用的 LRU 算法(最近最少使用)來判斷緩存是否在內(nèi)存占滿時(shí)被清除。
直接復(fù)制下春哥的示例代碼:
local _M = {}
-- alternatively: local lrucache = require "resty.lrucache.pureffi"
local lrucache = require "resty.lrucache"
-- we need to initialize the cache on the Lua module level so that
-- it can be shared by all the requests served by each nginx worker process:
local c = lrucache.new(200) -- allow up to 200 items in the cache
if not c then
return error("failed to create the cache: " .. (err or "unknown"))
end
function _M.go()
c:set("dog", 32)
c:set("cat", 56)
ngx.say("dog: ", c:get("dog"))
ngx.say("cat: ", c:get("cat"))
c:set("dog", { age = 10 }, 0.1) -- expire in 0.1 sec
c:delete("dog")
end
return _M
可以看出來,這個(gè) cache 是 worker 級(jí)別的,不會(huì)在 Nginx wokers 之間共享。并且,它是預(yù)先分配好 key 的數(shù)量,而 shared dict 需要自己用 key 和 value 的大小和數(shù)量,來估算需要把內(nèi)存設(shè)置為多少。
shared.dict 使用的是共享內(nèi)存,每次操作都是全局鎖,如果高并發(fā)環(huán)境,不同 worker 之間容易引起競(jìng)爭(zhēng)。所以單個(gè) shared.dict 的體積不能過大。lrucache 是 worker 內(nèi)使用的,由于 Nginx 是單進(jìn)程方式存在,所以永遠(yuǎn)不會(huì)觸發(fā)鎖,效率上有優(yōu)勢(shì),并且沒有 shared.dict 的體積限制,內(nèi)存上也更彈性,但不同 worker 之間數(shù)據(jù)不同享,同一緩存數(shù)據(jù)可能被冗余存儲(chǔ)。
你需要考慮的,一個(gè)是 Lua lru cache 提供的 API 比較少,現(xiàn)在只有 get、set 和 delete,而 ngx shared dict 還可以 add、replace、incr、get_stale(在 key 過期時(shí)也可以返回之前的值)、get_keys(獲取所有 key,雖然不推薦,但說不定你的業(yè)務(wù)需要呢);第二個(gè)是內(nèi)存的占用,由于 ngx shared dict 是 workers 之間共享的,所以在多 worker 的情況下,內(nèi)存占用比較少。