程序员的喵

Kong 源码分析:缓存

2017-07-22

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 刚好把数据更新到了缓存里。

公号同步更新,欢迎关注👻
Tags: Lua