前些天帮别人优化PHP程序,搞得灰头土脸,最后黔驴技穷开启了FastCGI Cache,算是勉强应付过去了吧。不过FastCGI Cache不支持分布式缓存,当服务器很多的时候,冗余的浪费将非常严重,此外还有数据一致性问题,所以它只是一个粗线条的解决方案。

对此类问题而言,SRCache是一个细粒度的解决方案。其工作原理大致如下:

SRCache工作原理

当问题比较简单的时候,通常SRCache和Memc模块一起搭配使用。网上能搜索到一些相关的例子,大家可以参考,这里就不赘述了。当问题比较复杂的时候,比如说缓存键的动态计算等,就不得不写一点代码了,此时Lua模块是最佳选择。

闲言碎语不多讲,表一表Nginx配置文件长啥样:

lua_shared_dict phoenix_status 100m;

lua_package_path '/path/to/phoenix/include/?.lua;/path/to/phoenix/vendor/?.lua;;';

init_by_lua_file /path/to/phoenix/config.lua;

server {
    listen 80;

    server_name foo.com;

    root /path/to/root;

    index index.html index.htm index.php;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        set $phoenix_key "";
        set $phoenix_fetch_skip 1;
        set $phoenix_store_skip 1;

        rewrite_by_lua_file /path/to/phoenix/monitor.lua;

        srcache_fetch_skip $phoenix_fetch_skip;
        srcache_store_skip $phoenix_store_skip;

        srcache_fetch GET /phoenix/content key=$phoenix_key;
        srcache_store PUT /phoenix/content key=$phoenix_key;

        add_header X-SRCache-Fetch-Status $srcache_fetch_status;
        add_header X-SRCache-Store-Status $srcache_store_status;

        try_files $uri =404;

        include fastcgi.conf;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_intercept_errors on;

        error_page 500 502 503 504 = /phoenix/failover;
    }

    location = /phoenix/content {
        internal;
        content_by_lua_file /path/to/phoenix/content.lua;
    }

    location = /phoenix/failover {
        internal;
        rewrite_by_lua_file /path/to/phoenix/failover.lua;
    }
}

Nginx启动后,会载入config.lua中的配置信息。请求到达后,缺省情况下,SRCache为关闭状态,在monitor.lua中,会对当前请求进行正则匹配,一旦匹配成功,那么就会计算出缓存键,并且把SRCache设置为开启状态,最后由content.lua完成读写。

看看「config.lua」文件的内容,它主要用来记录一些全局的配置信息:

phoenix = {}

phoenix["memcached"] = {
    default = {
        timeout = "100",
        keepalive = {idle = 10000, size = 100},
    },
    {host = "127.0.0.1", port = "11211"},
    {host = "127.0.0.1", port = "11212"},
    {host = "127.0.0.1", port = "11213"},
}

phoenix["rule"] = {
    default = {
        expire = 600,
        min_uses = 0,
        max_errors = 0,
        query = {
            ["debug"] = false,
        },
    },
    {
        regex = "^/foo/bar",
        query = {
            ["page"] = function(v)
                if v == "" or v == nil then
                    return 1
                end

                return tonumber(v) or false
            end,
            ["limit"] = true,
        },
    },
}

看看「monitor.lua」文件的内容,它主要用来计算缓存键,并开启SRCache模块:

local status = require "status"

local status = status:new(ngx.shared.phoenix_status)

local request_uri_without_args = ngx.re.sub(ngx.var.request_uri, "\\?.*", "")

table.unpack = table.unpack or unpack

for index, rule in ipairs(phoenix["rule"]) do
    if type(rule["regex"]) == "string" then
        rule["regex"] = {rule["regex"], ""}
    end

    local regex, options = table.unpack(rule["regex"])

    if ngx.re.match(request_uri_without_args, regex, options) then
        local default = phoenix["rule"]["default"]

        local expire     = rule["expire"]     or default["expire"]
        local min_uses   = rule["min_uses"]   or default["min_uses"]
        local max_errors = rule["max_errors"] or default["max_errors"]

        local key = {
            ngx.var.request_method, " ",
            ngx.var.scheme, "://",
            ngx.var.host, request_uri_without_args,
        }

        rule["query"] = rule["query"] or {}

        if default["query"] then
            for key, value in pairs(default["query"]) do
                if not rule["query"][key] then
                    rule["query"][key] = value
                end
            end
        end

        local query = {}

        local args = ngx.req.get_uri_args()

        for name, value in pairs(rule["query"]) do
            if type(value) == "function" then
                value = value(args[name])
            end

            if value == true then
                value = args[name]
            end

            if value then
                query[name] = value
            elseif args[name] then
                return
            end
        end

        query = ngx.encode_args(query)

        if query ~= "" then
            key[#key + 1] = "?"
            key[#key + 1] = query
        end

        key = table.concat(key)
        key = ngx.md5(key)

        ngx.var.phoenix_key = key

        local now = ngx.time()

        if ngx.var.arg_phoenix == true then
            ngx.var.phoenix_fetch_skip = 0
        else
            for i = 0, 1 do
                local errors = status:get_errors(index, now - i * 60)

                if errors >= max_errors then
                    ngx.var.phoenix_fetch_skip = 0
                    break
                end
            end
        end

        local uses = status:incr_uses(key, 1)

        if uses >= min_uses then
            local timestamp = status:get_timestamp(key)

            if now - timestamp >= expire then
                ngx.var.phoenix_store_skip = 0
            end
        end

        break
    end
end

看看「content.lua」文件的内容,它主要通过Resty库来读写Memcached:

local memcached = require "resty.memcached"
local status    = require "status"

local status = status:new(ngx.shared.phoenix_status)

local key = ngx.var.arg_key

local index = ngx.crc32_long(key) % #phoenix["memcached"] + 1

local config  = phoenix["memcached"][index]
local default = phoenix["memcached"]["default"]

local host      = config["host"]      or default["host"]
local port      = config["port"]      or default["port"]
local timeout   = config["timeout"]   or default["timeout"]
local keepalive = config["keepalive"] or default["keepalive"]

local memc, err = memcached:new()

if not memc then
    ngx.log(ngx.ERR, err)
    ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end

if timeout then
    memc:set_timeout(timeout)
end

local ok, err = memc:connect(host, port)

if not ok then
    ngx.log(ngx.ERR, err)
    ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end

local method = ngx.req.get_method()

if method == "GET" then
    local res, flags, err = memc:get(key)

    if err then
        ngx.log(ngx.ERR, err)
        ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
    end

    if res == nil and flags == nil and err == nil then
        ngx.exit(ngx.HTTP_NOT_FOUND)
    end

    ngx.print(res)
elseif method == "PUT" then
    local value = ngx.req.get_body_data()
    local expire = ngx.var.arg_expire or 86400

    local ok, err = memc:set(key, value, expire)

    if not ok then
        ngx.log(ngx.ERR, err)
        ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
    end

    status:set_timestamp(key)
else
    ngx.exit(ngx.HTTP_NOT_ALLOWED)
end

if type(keepalive) == "table" then
    if keepalive["idle"] and keepalive["size"] then
        memc:set_keepalive(keepalive["idle"], keepalive["size"])
    end
end

看看「failover.lua」文件的内容,它是为了在出错时激活容灾模式:

ngx.req.set_uri_args(ngx.var.args .. "&phoenix")
ngx.req.set_uri(ngx.var.uri, true)

此外,还有一个「status.lua」文件:

local status = {}

local get_timestamp_key = function(key)
    key = {
        "phoenix", "status", "timestamp", key,
    }

    return table.concat(key, ":")
end

local get_uses_key = function(key, timestamp)
    key = {
        "phoenix", "status", "uses", key, os.date("%Y%m%d%H%M", timestamp),
    }

    return table.concat(key, ":")
end

local get_errors_key = function(key, timestamp)
    key = {
        "phoenix", "status", "errors", key, os.date("%Y%m%d%H%M", timestamp),
    }

    return table.concat(key, ":")
end

local get = function(shared, key)
    return shared:get(key)
end

local set = function(shared, key, value, expire)
    return shared:set(key, value, expire or 86400)
end

local incr = function(shared, key, value, expire)
    value = value or 1

    local counter = shared:incr(key, value)

    if not counter then
        shared:add(key, 0, expire or 86400)
        counter = shared:incr(key, value)
    end

    return counter
end

function status:new(shared)
    return setmetatable({shared = shared}, {__index = self})
end

function status:get_timestamp(key)
    return get(self.shared, get_timestamp_key(key)) or 0
end

function status:set_timestamp(key, value, expire)
    key = get_timestamp_key(key)
    value = value or ngx.time()

    return set(self.shared, key, value, expire)
end

function status:get_uses(key, timestamp)
    timestamp = timestamp or ngx.time()
    key = get_uses_key(key, timestamp)

    return get(self.shared, key) or 0
end

function status:incr_uses(key, value, expire)
    key = get_uses_key(key, ngx.time())
    value = value or 1

    return incr(self.shared, key, value, expire)
end

function status:get_errors(key, timestamp)
    timestamp = timestamp or ngx.time()
    key = get_errors_key(key, timestamp)

    return get(self.shared, key) or 0
end

function status:incr_errors(key, value, expire)
    key = get_errors_key(key, ngx.time())
    value = value or 1

    return incr(self.shared, key, value, expire)
end

return status

最后一个问题:如何判断缓存是否生效了?试试下面的命令:

shell> curl -v "http://foo.com/test?x=123&y=abc"
< X-SRCache-Fetch-Status: HIT
< X-SRCache-Store-Status: BYPASS

目前我主要用srcache来缓存一些接口的json结果集,这些接口同时也支持jsonp,也就是客户端传递一个callback参数之类的,大家应该明白,此时如果不加区分的都缓存,那么有callback的和没有callback的调用结果就都要保存起来了,内存占用直接翻番,可实际上它们的内容大同小异,所以在实际应用时,我们应该仅仅缓存没有callback的数据,而对于有callback的请求,可以用xss-nginx-module来搞定。

关于激活SRCache前后的性能对比,视环境的不同会有所差异,不过绝对是数量级的提升,更重要的是这一切对业务层完全透明,别愣着了,快试试吧!

Nginx缓存解决方案:SRCache的更多相关文章

  1. 【快学SpringBoot】Spring Cache+Redis实现高可用缓存解决方案

    前言 之前已经写过一篇文章介绍SpringBoot整合Spring Cache,SpringBoot默认使用的是ConcurrentMapCacheManager,在实际项目中,我们需要一个高可用的. ...

  2. CRL快速开发框架系列教程六(分布式缓存解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  3. ASP.NET Core 缓存技术 及 Nginx 缓存配置

    前言 在Asp.Net Core Nginx部署一文中,主要是讲述的如何利用Nginx来实现应用程序的部署,使用Nginx来部署主要有两大好处,第一是利用Nginx的负载均衡功能,第二是使用Nginx ...

  4. nginx缓存设置proxy_cache

    http://www.cnblogs.com/dudu/p/4597351.html http块: proxy_cache_path /tmp/cache levels=1:2 keys_zone=n ...

  5. nginx缓存配置的操作记录梳理

    web缓存位于内容源Web服务器和客户端之间,当用户访问一个URL时,Web缓存服务器会去后端Web源服务器取回要输出的内容,然后,当下一个请求到来时,如果访问的是相同的URL,Web缓存服务器直接输 ...

  6. nginx 缓存机制

    nginx 缓存机制   Nginx缓存的基本思路 利用请求的局部性原理,将请求过的内容在本地建立一个副本,下次访问时不再连接到后端服务器,直接响应本地内容 Nginx服务器启动后,会对本地磁盘上的缓 ...

  7. Nginx 缓存参数

    看看这下面两个指令参数: ----------------------------------------------------------------- proxy_cache_path  /ho ...

  8. 分布式Nginx缓存清理(PHP的socket编程)

    最近,公司要使用康乐的几台自建CDN换成Nginx,在缓存配置上不会有很多的问题,纠结的问题是:Nginx的如何批量进行缓存清理 我们都知道Nginx提供了一个第三方的模块"nginx ng ...

  9. nginx缓存优先级(缓存问题者必看)

    接触nginx的兄弟或多或少都有遇到缓存问题,要么是nginx为什么不缓存,要么就是nginx缓存很快就失效等等问题,在网上找了一遍nginx缓存优先级的文章,大家可以参考下. 架构图client端  ...

随机推荐

  1. [Javascript] What is JavaScript Function Currying?

    Currying is a core concept of functional programming and a useful tool for any developer's toolbelt. ...

  2. 窥探 Swift 之 函数与闭包的应用实例

    今天的博客算是比较基础的,还是那句话,基础这东西在什么时候 都是最重要的.说到函数,只要是写过程序就肯定知道函数是怎么回事,今天就来讨论一下Swift中的函数的特性以及Swift中的闭包.今天的一些小 ...

  3. 判断两条直线的位置关系 POJ 1269 Intersecting Lines

    两条直线可能有三种关系:1.共线     2.平行(不包括共线)    3.相交. 那给定两条直线怎么判断他们的位置关系呢.还是用到向量的叉积 例题:POJ 1269 题意:这道题是给定四个点p1, ...

  4. 5、第5节课CSS补充和html 标签讲解20150924

    1. DIV 隐藏 A: 隐藏之后不占位置  display:none; B:隐藏之后占位置 visibility:hidden; 2.DIV 排序 z-index:2;  默认是1,如果想DIV在上 ...

  5. javascript基础之for循环

    1.数组定义声名 var arry = [1,2,3,4,5]   //相当与var arry = Array(1,2,3,4,5) 2.数据的增删改查 var arry = [1,2,3,4,5] ...

  6. sunny day

    初始学习记录 基于http://www.htmleaf.com/html5/html5muban/20141121552.html模板 <!DOCTYPE html> <html l ...

  7. 【转】Debug和Release区别

    VC下Debug和Release区别 最近写代码过程中,发现 Debug 下运行正常,Release 下就会出现问题,百思不得其解,而Release 下又无法进行调试,于是只能采用printf方式逐步 ...

  8. div+css的叫法是不正确的

    为什么国人将这种布局标准页面的方法叫做DIV+CSS? 因为第一个将这种技术引进中国的人,对这门技术理解不够透彻,单纯从代码上辨别过去的页面布局方法和现在流行的页面布局方法,认为过去布局页面用的是Ta ...

  9. IIS 中asp.net的一些配置

    安装了IIS之后, 添加了虚拟目录然后运行页面, 出现了一点儿错误, 好像是不认识aspx文件, 把aspx文件当成是xml文件处理. 无法显示 XML 页. 使用 XSL 样式表无法查看 XML 输 ...

  10. OpenStack虚机相关错误

    OpenStack配置起来还是挺麻烦的,特别是网络那块.虽然官方文档越来越清晰,但有时还是会出各种错.排错主要是看日志.看官方文档和google 以下就一些虚机相关常见的错误做一下总结(基于Iceho ...