Nginx 里的缓存使用
在 Kong 里面我们缓存的内容大部分是配置,不管是 API 本身的配置还是插件相关的配置,缓存之后就存储在内存中。
Kong 里的缓存基础代码在 tools/database_cache.lua 文件里面。这里又分两种类型的缓存,一种是shared dict, 一种是使用
lua-resty-lrucache。这两者之间是有区别的:shared dict 如同其名字一样是 Nginx worker 之间共享的,而 lrucache 是 worker 级别的,内存空间在 Lua VM 里由 GC 管理,不能在进程之间共享,自然也不会在 Nginx worker 之间共享。
具体我们开发中使用哪一种由具体场景分析,比如在 Kong 的插件 rate-limiting 里就使用了共享缓存,因为我们需要针对一个 Nginx 所有的 worker 做请求数统计。
share dict 最常规的使用方法是:
http {
lua_shared_dict dogs 10m;
server {
location /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
ngx.say("STORED")
}
}
location /get {
content_by_lua_block {
local dogs = ngx.shared.dogs
ngx.say(dogs:get("Jim"))
}
}
}
}
lrucache 的使用方法如文档所示。
Kong 里的多级缓存实现
有了上面的了解,看 database_cache.lua 这个文件就比较直观了,这里 Kong 会分多类缓存:apis, consumers, plugins 等。具体这样分是因为如果我们对配置做了修改,需要发出 serf 消息来指名这次改动涉及到哪些,其他 Kong 节点收到消息后自然只更新对应的缓存部分。所以 Kong 里申明了一个列表 CACHE_KEYS 来存要缓存的数据类别,同时写了不少生成缓存 key 的方法,比如:api_key,plugin_key 等。
仔细查看 database_cache.lua,我们发现其实这里是做了两级缓存。Kong 要从缓存里取出一个 key/value,首先从 lrucache 里取,如果有则返回。如果没有则从 share dict 里去取,如果取到则 deserialize 然后存储在 lrucache 里,然后返回。如果 shared dict 里也没有,则返回 nil。标准的两级缓存流程,这样做的好处在于减少 deserialize 的次数,而且 shared dict 可能被多个 worker 同时修改,要修改的时候需要加互斥锁。
这里最常用的方法是 get_or_set,尝试获取一个 key 的值,如果没有就执行对应的 callback,返回结果当做 value 设置到缓存里,并把 value 作为最后的返回结果。这里的 callback 函数通常做的当然是从数据库里读取内容。
如何避免缓存失效风暴
我们在实现缓存的时候缓存失效风暴问题需要谨慎考虑。agentzh 在这里详细描述了加锁解决的策略,ngx.shcache这里也使用了相同的方法,具体可以好好研究一下那个图。
主要注意的是在加锁后,再尝试去读取一次 key,因为可能在加锁之前其他 worker 刚好把数据更新到了缓存里。