Nginx 用 Lua 控制 Fastcgi cache 缓存实现服务优雅降级

技巧 shanhuhai 114℃ 0评论

如果服务都是动态页面没有做静态化,当某个页面转发很高,访问量很大,可能会有很高的瞬时并发请求进到php-fpm 中,导致数据库和 php-fpm 崩溃。

这种情况下要不就是加服务器提升并发,要不就是优化程序性能,但都是事后手段了。

这里我们提供一种弹性的可以根据用户并发请求量来触发的服务降级方式,请求正常时,缓存并部启用,当并发请求量高时,Nginx 自带的 Fastcgi cache 将被触发启用,将 php-fpm 返回的内容缓存,后续的请求如果是已经被缓存过的请求地址可以将缓存中的内容直接返回,避免了请求进入到 php-fpm 中导致 数据库 和 php-fpm 崩溃。

下面是实现方式:

首先 nginx 要先安装 lua 模块: lua-nginx-module
或者你已经使用了 openresty 也可以

1.创建 nginx 缓存目录

mkdir /tmp/ngx_cache

2.打开 nginx.conf

http 块定义一个缓存块,

log_format fs '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$upstream_cache_status"';
lua_shared_dict reqqs 10m;
lua_shared_dict cachemap 10m;
fastcgi_cache_path 
    /tmp/ngx_cache
    levels=1:2
    keys_zone=mcontent:1000m
    inactive=20m
    max_size=1g;

log_format 指令用于自定义一个nginx access log 的日志格式,方便我们统计Fastcgi cache的命中率等
lua_shared_dict 用于定义一块共享内存字典,这个字典是在 nginx 的所有worker 都可以共享的, 这里定义了两个共享内存字典,”reqqs” 用于计数单个请求在指定时间段内的请求次数, “cachemap” 用于标记哪些请求地址是要走缓存的

关于 fastcgi_cache_path 指令参考: Nginx 开启Fastcgi 缓存

3. 在 Nginx 的配置中找到解析 php 脚本的 location 块加入以下代码 :

        set $cache_bypass "1";
        set_by_lua $use_cache '
           local function getextension(filename)
                return filename:match(".+%.(%w+)")
           end
           local function stripfilename(filename)
                return string.match(filename, "(.+)%??.*$")
           end

           local reqqs = ngx.shared.reqqs
           local cachemap = ngx.shared.cachemap
           local uri = stripfilename(ngx.var.request_uri)

           -- if request method is not "GET" then bypass
           if ngx.var.request_method ~= "GET" then
                return "1"
           end

           -- check request uri and extension
           local ext = getextension(uri)
           if ext ~= "html" and ext ~= "shtml" and ext ~= "htm" then
                return "1"
           end

           -- if in cache then use cache
           local cached = cachemap:get(uri)
           if cached then
               return "0"
           end

           -- requested 2 times in 1 sec then cache 3 min
           local num = reqqs:get(uri)
           if num then 
                if num >= 2 then
                    cachemap:set(uri, 1, 180)
                    return "0"
                else
                    reqqs:incr(uri,1)
                    return "1"
                end
           else
                reqqs:set(uri,1,2)
                return "1"
           end
    ';

        fastcgi_cache mcontent;
        fastcgi_cache_valid 200 301 302 3m;
        fastcgi_cache_min_uses 1;
        fastcgi_cache_lock on;
        fastcgi_cache_lock_timeout 3s;
        fastcgi_cache_use_stale
            error
            timeout
            invalid_header
            updating
            http_500
            http_503;
        fastcgi_cache_key $request_method://$host$request_uri;
        fastcgi_cache_bypass $use_cache;
        fastcgi_ignore_headers "Cache-Control" "Expires" "Set-Cookie";
        add_header X-Cache-Status $upstream_cache_status;

        access_log /var/log/server/tengine/user.{{domain}}.fs.log fs;

以上脚本实现了所有以 htmlhtmshtml后缀结尾的请求,如果被1秒内请求了两次,则启用该请求的 fastcgi 缓存 3分钟。

其中比较重要的参数有 fastcgi_cache_lock , 这个参数设为 “on”, 如果多个瞬时并发请求,如果缓存还没有生成,则只有第一个请求会进到 php-fpm 中, 当缓存生成后其他请求再会被响应,这样可以避免缓存失效时,并发请求将php-fpm 压垮。

fastcgi_cache_bypass 用于决定请求是否要走 Fastcgi 缓存, 如果为非0的值则不走缓存。

向春哥的 openresty 致敬.

转载请注明:大后端 » Nginx 用 Lua 控制 Fastcgi cache 缓存实现服务优雅降级

喜欢 (0)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址