Nginx官方自带了非常多的核心模块再加上第三方的模块能够满足我们大部分的业务需要,但是业务的需求、业务的场景变化需要添加些额外的功能,如果自己去开发一个nginx模块相对来说比较笨重,我们可以使用lua脚本直接内嵌到nginx当中实现一些业务逻辑,完成一些特殊的功能需求。
ngx_lua是Nginx的一个模块,将Lua嵌入到Nginx中,从而可以使用Lua来编写脚本,这样就可以使用Lua编写应用脚本,部署到Nginx中运行
我们选择使用OpenResty,因为他是由Nginx核心加很多第三方模块组成,其最大的亮点是默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用。 而且OpenResty提供了大量组件如Mysql、Redis、Memcached等等,使在Nginx上开发更方便更简单,不需要我们去引入集成。
http://opm.openresty.org/ Openresty类库
lua+Redis集群
GitHub:https://github.com/steve0511/resty-redis-cluster
通过上面的地址下载扩展包,并在Nginx的配置文件中引入
lua_package_path "/path/lualib/?.lua;"; lua_package_cpath "/path/lualib/?.so;";
/path/lualib/为引入路径(安装详情通过上述地址查看)
以下为通过lua文件,建立redis集群链接
local config = {
name = "testCluster", --rediscluster name
serv_list = {
--redis cluster node list(host and port),
{
ip = "127.0.0.1", port = 7001 },
{
ip = "127.0.0.1", port = 7002 },
{
ip = "127.0.0.1", port = 7003 },
{
ip = "127.0.0.1", port = 7004 },
{
ip = "127.0.0.1", port = 7005 },
{
ip = "127.0.0.1", port = 7006 }
},
keepalive_timeout = 60000, --redis connection pool idle timeout
keepalive_cons = 1000, --redis connection pool size
connection_timout = 1000, --timeout while connecting
max_redirection = 5, --maximum retry attempts for redirection,
auth = "pass" --set password while setting auth
}
local redis_cluster = require "rediscluster"
local red_c = redis_cluster:new(config)
local v, err = red_c:get("name")
if err then
ngx.log(ngx.ERR, "err: ", err)
else
ngx.say(v)
end
多级缓存
一般来说,缓存有两个原则。
一是越靠近用户的请求越好。比如,能用本地缓存的就不要发送 HTTP 请求,能用 CDN 缓存的就不要打到源站,能用 OpenResty 缓存的就不要查询数据库。
二是尽量使用本进程和本机的缓存解决。因为跨了进程和机器甚至机房,缓存的网络开销 就会非常大,这一点在高并发的时候会非常明显
在 OpenResty 中,缓存的设计和使用也遵循这两个原则。OpenResty 中有两个缓存的组件:shared_dict 缓存和 lru 缓存。前者只能缓存字符串对象,缓存的数据有且只有 一份,每一个 worker 都可以进行访问,所以常用于 worker 之间的数据通信。后者则可以 缓存所有的 Lua 对象,但只能在单个 worker 进程内访问,有多少个 worker,就会有多少 份缓存数据。
lua_shared_dict redis_cluster_slot_locks 100k;
lua_shared_dict redis_cluster_addr 20k;
lua_shared_dict my_cache 1M;
lua_shared_dict my_locks 100k;
lua_shared_dict ipc_cache 1M;
我们可以在Nginx的配置文件中预先定义好共享缓存,然后可以在.lua文件中通过ngx.shared.+缓存名称的方式获取。
缓存击穿问题
数据源在 MySQL 数据库中,缓存的数据放在共享字典中,超时时间为1分钟。在这 1分钟内的时间里,所有的请求都从缓存中获取数据,MySQL 没有任何的压力。但是,一旦到达 1分钟,也就是缓存数据失效的那一刻,如果正好有大量的并发请求进来,在缓存中没有查询到结果,就要触发查询数据源的函数,那么这些请求全部都将去查询 MySQL 数据库, 直接造成数据库服务器卡顿,甚至卡死。
解决方式
- 获取锁,直到成功或超时。如果超时,则抛出异常,返回。如果成功,继续向下执行。
- 再去缓存中。如果存在值,则直接返回;如果不存在,则继续往下执行如果成功获取到锁的话,就可以保证只有一个请求去数据源更新数据,并更新到缓存中了。
- 查询 DB ,并更新到缓存中,返回值。
Openresty可以利用lua-resty-lock 加锁,利用的是OpenResty 自带的 resty 库.不过,在上面 lua-resty-lock 的实现中,你需要自己来处理加锁、解锁、获取过期数据、重试、异常处理等各种问题,还是相当繁琐的 我们可以使用lua-resty-mlcache,
更多的定义及使用方法请进入GitHub地址查看详细文档
mlcache工作原理大致如下:
- L1:最近使用Lua resty lrucache的Lua VM缓存最少。提供最快的查找(如果已填充),并避免耗尽工作人员的Lua VM内存。
- L2:lua_shared_dict内存区域由所有工作人员共享。仅当一级未命中时才访问此级别,并阻止工作人员请求三级缓存。
- L3:去redis或存储中读取数据,并将其设置为二级缓存供其他人员使用
local key=ngx.re.match(ngx.var.request_uri,"/([0-9]+).html") //正则匹配请求的uri地址
local mlcache= require "resty.mlcache"//引用mlcache包
local lrucache = require "resty.lrucache"//引用lrucache包
lru = lrucache.new(100)//创建一个缓存实例,100为最大存储数量
lru:set(key, "111", ttl)//设置缓存key,value,过期时间
ngx.header.content_type="text/plain"
-- L3的回调
local function fetch_shop(key,cache)
return "id=1"
end
if type(key) == "table" then //匹配请求url的类型(table)
local cache,err=mlcache.new("cache_name","my_cache",{
//新建mlcahe缓存
lru_size = 500, --设置的缓存的个数
ttl = 5, --缓存过期时间
neg_ttl =