Chinaunix首页 | 论坛 | 博客
  • 博客访问: 866248
  • 博文数量: 182
  • 博客积分: 1992
  • 博客等级: 上尉
  • 技术积分: 1766
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-18 11:49
文章分类

全部博文(182)

文章存档

2019年(1)

2016年(5)

2015年(29)

2014年(38)

2013年(21)

2012年(36)

2011年(52)

我的朋友

分类: 系统运维

2015-09-02 10:49:11

SRCache是一个细粒度的解决方案。其工作原理大致如下:
SRCache工作原理

SRCache工作原理

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

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

 

点击(此处)折叠或打开

  1. lua_shared_dict phoenix_status 100m;
  2. lua_package_path '/path/to/phoenix/include/?.lua;/path/to/phoenix/vendor/?.lua;;';
  3. init_by_lua_file /path/to/phoenix/config.lua;
  4. server {
  5.     listen 80;
  6.     server_name foo.com;
  7.     root /path/to/root;
  8.     index index.html index.htm index.php;
  9.     location / {
  10.         try_files $uri $uri/ /index.php$is_args$args;
  11.     }
  12.     location ~ \.php$ {
  13.         set $phoenix_key "";
  14.         set $phoenix_fetch_skip 1;
  15.         set $phoenix_store_skip 1;
  16.         rewrite_by_lua_file /path/to/phoenix/monitor.lua;
  17.         srcache_fetch_skip $phoenix_fetch_skip;
  18.         srcache_store_skip $phoenix_store_skip;
  19.         srcache_fetch GET /phoenix/content key=$phoenix_key;
  20.         srcache_store PUT /phoenix/content key=$phoenix_key;
  21.         add_header X-SRCache-Fetch-Status $srcache_fetch_status;
  22.         add_header X-SRCache-Store-Status $srcache_store_status;
  23.         try_files $uri =404;
  24.         include fastcgi.conf;
  25.         fastcgi_pass 127.0.0.1:9000;
  26.         fastcgi_intercept_errors on;
  27.         error_page 500 502 503 504 = /phoenix/failover;
  28.     }
  29.     location = /phoenix/content {
  30.         internal;
  31.         content_by_lua_file /path/to/phoenix/content.lua;
  32.     }
  33.     location = /phoenix/failover {
  34.         internal;
  35.         rewrite_by_lua_file /path/to/phoenix/failover.lua;
  36.     }
  37. }

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

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

 

点击(此处)折叠或打开

  1. phoenix = {}
  2. phoenix["memcached"] = {
  3.     default = {
  4.         timeout = "100",
  5.         keepalive = {idle = 10000, size = 100},
  6.     },
  7.     {host = "127.0.0.1", port = "11211"},
  8.     {host = "127.0.0.1", port = "11212"},
  9.     {host = "127.0.0.1", port = "11213"},
  10. }
  11. phoenix["rule"] = {
  12.     default = {
  13.         expire = 600,
  14.         min_uses = 0,
  15.         max_errors = 0,
  16.         query = {
  17.             ["debug"] = false,
  18.         },
  19.     },
  20.     {
  21.         regex = "^/foo/bar",
  22.         query = {
  23.             ["page"] = function(v)
  24.                 if v == "" or v == nil then
  25.                     return 1
  26.                 end
  27.                 return tonumber(v) or false
  28.             end,
  29.             ["limit"] = true,
  30.         },
  31.     },
  32. }

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

 

点击(此处)折叠或打开

  1. local status = require "status"
  2. local status = status:new(ngx.shared.phoenix_status)
  3. local request_uri_without_args = ngx.re.sub(ngx.var.request_uri, "\\?.*", "")
  4. table.unpack = table.unpack or unpack
  5. for index, rule in ipairs(phoenix["rule"]) do
  6.     if type(rule["regex"]) == "string" then
  7.         rule["regex"] = {rule["regex"], ""}
  8.     end
  9.     local regex, options = table.unpack(rule["regex"])
  10.     if ngx.re.match(request_uri_without_args, regex, options) then
  11.         local default = phoenix["rule"]["default"]
  12.         local expire = rule["expire"] or default["expire"]
  13.         local min_uses = rule["min_uses"] or default["min_uses"]
  14.         local max_errors = rule["max_errors"] or default["max_errors"]
  15.         local key = {
  16.             ngx.var.request_method, " ",
  17.             ngx.var.scheme, "://",
  18.             ngx.var.host, request_uri_without_args,
  19.         }
  20.         rule["query"] = rule["query"] or {}
  21.         if default["query"] then
  22.             for key, value in pairs(default["query"]) do
  23.                 if not rule["query"][key] then
  24.                     rule["query"][key] = value
  25.                 end
  26.             end
  27.         end
  28.         local query = {}
  29.         local args = ngx.req.get_uri_args()
  30.         for name, value in pairs(rule["query"]) do
  31.             if type(value) == "function" then
  32.                 value = value(args[name])
  33.             end
  34.             if value == true then
  35.                 value = args[name]
  36.             end
  37.             if value then
  38.                 query[name] = value
  39.             elseif args[name] then
  40.                 return
  41.             end
  42.         end
  43.         query = ngx.encode_args(query)
  44.         if query ~= "" then
  45.             key[#key + 1] = "?"
  46.             key[#key + 1] = query
  47.         end
  48.         key = table.concat(key)
  49.         key = ngx.md5(key)
  50.         ngx.var.phoenix_key = key
  51.         local now = ngx.time()
  52.         if ngx.var.arg_phoenix == true then
  53.             ngx.var.phoenix_fetch_skip = 0
  54.         else
  55.             for i = 0, 1 do
  56.                 local errors = status:get_errors(index, now - i * 60)
  57.                 if errors >= max_errors then
  58.                     ngx.var.phoenix_fetch_skip = 0
  59.                     break
  60.                 end
  61.             end
  62.         end
  63.         local uses = status:incr_uses(key, 1)
  64.         if uses >= min_uses then
  65.             local timestamp = status:get_timestamp(key)
  66.             if now - timestamp >= expire then
  67.                 ngx.var.phoenix_store_skip = 0
  68.             end
  69.         end
  70.         break
  71.     end
  72. end

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

 

点击(此处)折叠或打开

  1. local memcached = require "resty.memcached"
  2. local status = require "status"
  3. local status = status:new(ngx.shared.phoenix_status)
  4. local key = ngx.var.arg_key
  5. local index = ngx.crc32_long(key) % #phoenix["memcached"] + 1
  6. local config = phoenix["memcached"][index]
  7. local default = phoenix["memcached"]["default"]
  8. local host = config["host"] or default["host"]
  9. local port = config["port"] or default["port"]
  10. local timeout = config["timeout"] or default["timeout"]
  11. local keepalive = config["keepalive"] or default["keepalive"]
  12. local memc, err = memcached:new()
  13. if not memc then
  14.     ngx.log(ngx.ERR, err)
  15.     ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
  16. end
  17. if timeout then
  18.     memc:set_timeout(timeout)
  19. end
  20. local ok, err = memc:connect(host, port)
  21. if not ok then
  22.     ngx.log(ngx.ERR, err)
  23.     ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
  24. end
  25. local method = ngx.req.get_method()
  26. if method == "GET" then
  27.     local res, flags, err = memc:get(key)
  28.     if err then
  29.         ngx.log(ngx.ERR, err)
  30.         ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
  31.     end
  32.     if res == nil and flags == nil and err == nil then
  33.         ngx.exit(ngx.HTTP_NOT_FOUND)
  34.     end
  35.     ngx.print(res)
  36. elseif method == "PUT" then
  37.     local value = ngx.req.get_body_data()
  38.     local expire = ngx.var.arg_expire or 86400
  39.     local ok, err = memc:set(key, value, expire)
  40.     if not ok then
  41.         ngx.log(ngx.ERR, err)
  42.         ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
  43.     end
  44.     status:set_timestamp(key)
  45. else
  46.     ngx.exit(ngx.HTTP_NOT_ALLOWED)
  47. end
  48. if type(keepalive) == "table" then
  49.     if keepalive["idle"] and keepalive["size"] then
  50.         memc:set_keepalive(keepalive["idle"], keepalive["size"])
  51.     end
  52. end

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

 

点击(此处)折叠或打开

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

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

 

点击(此处)折叠或打开

  1. local status = {}
  2. local get_timestamp_key = function(key)
  3.     key = {
  4.         "phoenix", "status", "timestamp", key,
  5.     }
  6.     return table.concat(key, ":")
  7. end
  8. local get_uses_key = function(key, timestamp)
  9.     key = {
  10.         "phoenix", "status", "uses", key, os.date("%Y%m%d%H%M", timestamp),
  11.     }
  12.     return table.concat(key, ":")
  13. end
  14. local get_errors_key = function(key, timestamp)
  15.     key = {
  16.         "phoenix", "status", "errors", key, os.date("%Y%m%d%H%M", timestamp),
  17.     }
  18.     return table.concat(key, ":")
  19. end
  20. local get = function(shared, key)
  21.     return shared:get(key)
  22. end
  23. local set = function(shared, key, value, expire)
  24.     return shared:set(key, value, expire or 86400)
  25. end
  26. local incr = function(shared, key, value, expire)
  27.     value = value or 1
  28.     local counter = shared:incr(key, value)
  29.     if not counter then
  30.         shared:add(key, 0, expire or 86400)
  31.         counter = shared:incr(key, value)
  32.     end
  33.     return counter
  34. end
  35. function status:new(shared)
  36.     return setmetatable({shared = shared}, {__index = self})
  37. end
  38. function status:get_timestamp(key)
  39.     return get(self.shared, get_timestamp_key(key)) or 0
  40. end
  41. function status:set_timestamp(key, value, expire)
  42.     key = get_timestamp_key(key)
  43.     value = value or ngx.time()
  44.     return set(self.shared, key, value, expire)
  45. end
  46. function status:get_uses(key, timestamp)
  47.     timestamp = timestamp or ngx.time()
  48.     key = get_uses_key(key, timestamp)
  49.     return get(self.shared, key) or 0
  50. end
  51. function status:incr_uses(key, value, expire)
  52.     key = get_uses_key(key, ngx.time())
  53.     value = value or 1
  54.     return incr(self.shared, key, value, expire)
  55. end
  56. function status:get_errors(key, timestamp)
  57.     timestamp = timestamp or ngx.time()
  58.     key = get_errors_key(key, timestamp)
  59.     return get(self.shared, key) or 0
  60. end
  61. function status:incr_errors(key, value, expire)
  62.     key = get_errors_key(key, ngx.time())
  63.     value = value or 1
  64.     return incr(self.shared, key, value, expire)
  65. end
  66. 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前后的性能对比,视环境的不同会有所差异,不过绝对是数量级的提升,更重要的是这一切对业务层完全透明,别愣着了,快试试吧!

阅读(835) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~