分类: 系统运维
2015-09-02 10:49:11
当问题比较简单的时候,通常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 " class="pln"> < X-SRCache-Fetch-Status: HIT < X-SRCache-Store-Status: BYPASS
目前我主要用srcache来缓存一些接口的json结果集,这些接口同时也支持jsonp,也就是客户端传递一个callback参数之类的,大家应该明白,此时如果不加区分的都缓存,那么有callback的和没有callback的调用结果就都要保存起来了,内存占用直接翻番,可实际上它们的内容大同小异,所以在实际应用时,我们应该仅仅缓存没有callback的数据,而对于有callback的请求,可以用xss-nginx-module来搞定。
关于激活SRCache前后的性能对比,视环境的不同会有所差异,不过绝对是数量级的提升,更重要的是这一切对业务层完全透明,别愣着了,快试试吧!