Skip to content

Instantly share code, notes, and snippets.

@lloydzhou
Last active April 25, 2022 06:02
Show Gist options
  • Save lloydzhou/d1dfc41f56866c4b82a6 to your computer and use it in GitHub Desktop.
Save lloydzhou/d1dfc41f56866c4b82a6 to your computer and use it in GitHub Desktop.
nginx srcache module to server stale data, using lua-resty-lock to make one request to create new cache, and using "lua-resty-http" + "ngx.timer.at" to update new cache in background.
upstream www {
server 127.0.0.1:9999;
}
upstream redis {
server 127.0.0.1:6379;
keepalive 1024;
}
lua_shared_dict srcache_locks 100k;
server {
listen 80;
server_name cache.example.com;
location / {
default_type application/json;
set $key $uri;
set_escape_uri $escaped_key $key;
set $staletime 10;
set $exptime 15;
rewrite_by_lua '
-- if has "X-Skip-Fetch" header will skip srcache_fetch, and create new cache
if ngx.var.http_x_skip_fetch then return end
local http, parser, lock = require "resty.http", require "redis.parser", require "resty.lock"
local key, method, stale = ngx.var.key, ngx.var.request_method, tonumber(ngx.var.stale) or 10
local res = ngx.location.capture("/redisttl", { args={key=key}})
local ttl = parser.parse_reply(res.body)
-- stale time, need update the cache
if ttl < stale then
-- cache missing, no need to using srcache_fetch, go to backend server, and store new cache
-- if redis server version is 2.8- can not return -2 !!!!!!
if ttl > -2 then
-- option: remove expire time for key
-- if ttl > 0 then
-- ngx.location.capture("/redispersist", { args={ key=key } })
-- end
-- get a lock, if success, do not fetch cache, create new one, the lock will release in "exptime".
-- if can not get the lock, just using the stale data from redis.
local l = lock:new("srcache_locks", {exptime=5, timeout=0.01})
if l and l:lock(key) then
if method == "POST" or method == "PUT" then ngx.req.read_body() end
local func = function(p, http, uri, method, headers, body)
headers["X-Skip-Fetch"] = "TRUE" -- set header force to create new cache.
-- pass the "Host" header, and so on
http:new():request({ url="http://127.0.0.1" .. uri, method=method, headers=headers, body=body})
ngx.log(ngx.ERR, "[LUA], success to update new cache, uri: ", uri, ", method: ", method)
end
-- run a backend task, to update new cache
ngx.timer.at(0, func, http, ngx.var.request_uri, method, ngx.req.get_headers(), ngx.req.get_body_data())
end
end
end
';
if ($http_x_skip_fetch != TRUE){ srcache_fetch GET /redis $key;}
srcache_store PUT /redis2 key=$escaped_key&exptime=$exptime;
add_header X-Cache $srcache_fetch_status;
add_header X-Store $srcache_store_status;
proxy_pass http://www;
}
location = /redisttl {
internal;
set_unescape_uri $key $arg_key;
set_md5 $key;
redis2_query ttl $key;
redis2_pass redis;
}
location = /redispersist {
internal;
set_unescape_uri $key $arg_key;
set_md5 $key;
redis2_query persist $key;
redis2_pass redis;
}
location = /redis {
internal;
set_md5 $redis_key $args;
redis_pass redis;
}
location = /redis2 {
internal;
set_unescape_uri $exptime $arg_exptime;
set_unescape_uri $key $arg_key;
set_md5 $key;
redis2_query set $key $echo_request_body;
redis2_query expire $key $exptime;
redis2_pass redis;
}
}
@youanden
Copy link

@lloydzhou There is a typo here:
https://gist.github.com/lloydzhou/d1dfc41f56866c4b82a6#file-nginx-conf-L26

You are referring to ngx.var.stale but created staletime

@lloydzhou
Copy link
Author

thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment