Nginx技術研究系列5-動態路由升級版

前幾篇文章咱們介紹了Nginx的配置、OpenResty安裝配置、基於Redis的動態路由以及Nginx的監控。html

Nginx-OpenResty安裝配置nginx

Nginx配置詳解git

Nginx技術研究系列1-經過應用場景看Nginx的反向代理github

Nginx技術研究系列2-基於Redis實現動態路由web

[原創]Nginx監控-Nginx+Telegraf+Influxb+Grafanaredis

在分佈式環境下,咱們要考慮高可用性和性能:算法

1. 不能由於Redis宕機影響請求的反向代理緩存

2. 每次請求若是都訪問Redis,性能有必定的損耗,同時Redis集羣的壓力隨着流量的激增不斷增長。app

所以,咱們要升級一下動態路由的設計方案。 咱們仍是先從應用場景出發:分佈式

1.提高性能,下降Redis的訪問頻率

2.Redis宕機不影響Nginx反向代理

實現上述兩個應用場景,咱們採用本地緩存+Redis緩存的雙緩存配合機制。

  • 第一次請求時,從Redis中獲取路由地址,而後放到本地緩存中,同時設置本地緩存項的有效時間
  • 後續請求時,從本地緩存直接獲取路由地址,若是本地緩存已經失效,則再次從Redis獲取路由地址,再放到本地緩存中。

肯定了上述實現方案後,咱們回顧一下咱們已有的Redis動態路由實現:

        upstream redis_cluster {
            server 192.0.1.*:6379;
            server 192.0.1.*:6379;
            server 192.0.1.*:6379;
        }

        location = /redis {
            internal;
            set_unescape_uri $key $arg_key;
            redis2_query get $key;
            redis2_pass redis_cluster;
        }

        location / {
            set $target '';
            access_by_lua '
                local query_string = ngx.req.get_uri_args()
                local sid = query_string["RequestID"]
                if sid == nil then
                    ngx.exit(ngx.HTTP_FORBIDDEN)
                end

                local key = sid
                local res = ngx.location.capture(
                    "/redis", { args = { key = key } }
                )

                if res.status ~= 200 then
                    ngx.log(ngx.ERR, "redis server returned bad status: ",
                        res.status)
                    ngx.exit(res.status)
                end

                if not res.body then
                    ngx.log(ngx.ERR, "redis returned empty body")
                    ngx.exit(500)
                end

                local parser = require "redis.parser"
                local server, typ = parser.parse_reply(res.body)
                if typ ~= parser.BULK_REPLY or not server then
                    ngx.log(ngx.ERR, "bad redis response: ", res.body)
                    ngx.exit(500)
                end

                if server == "" then
                    server = "default"
                end

                server = server .. ngx.var.request_uri
                ngx.var.target = server
            ';
                resolver 255.255.255.0;
                proxy_pass http://$target;
         }

咱們要在上述代碼中增長一層本地緩存實現,所以,咱們須要找一個本地緩存的實現Lib。

咱們在OpenResty的官網上找了一遍已提供的組件,發現了:lua-resty-lrucache - Lua-land LRU Cache based on LuaJIT FFI

Git Hub的地址:https://github.com/openresty/lua-resty-lrucache

嘗試寫了一下,發現這個cache實現是worker進程級別的,咱們Nginx是Auto的進程數配置,通常有4~8個,這個緩存每一個進程都New一個,貌似不符合咱們的要求,同時,這個cache是預分配好緩存的大小和數量,啓動的時候若是數量太多,分配內存很慢。

測試了一下,果然是這樣,所以,咱們繼續查找新的Cache實現。

ngx.shared.DICT,https://github.com/openresty/lua-nginx-module#ngxshareddict

這個 cache 是 nginx 全部 worker 之間共享的,內部使用的 LRU 算法(最近常常使用)來判斷緩存是否在內存佔滿時被清除。同時提供了以下方法:

而後,咱們基於這個組件實現了咱們升級版的Redis動態路由,直接上代碼Show:

        upstream redis_cluster {
            server 192.0.1.*:6379;
            server 192.0.1.*:6379;
            server 192.0.1.*:6379;
        }

 lua_shared_dict localcache 10m;——

        location = /redis {
            internal;
            set_unescape_uri $key $arg_key;
            redis2_query get $key;
            redis2_pass redis_cluster;
        }

        location / {
            set $target '';
            access_by_lua '
                local query_string = ngx.req.get_uri_args()
                local sid = query_string["RequestID"]
                if sid == nil then
                   ngx.exit(ngx.HTTP_FORBIDDEN)
                end

                local key = sid local cache = ngx.shared.localcache local server = cache:get(key) if server == nil then
                  local res = ngx.location.capture(
                    "/redis", { args = { key = key } }
                  )

                  if res.status ~= 200 then
                    ngx.log(ngx.ERR, "redis server returned bad status: ",
                        res.status)
                    ngx.exit(res.status)
                  end

                  if not res.body then
                    ngx.log(ngx.ERR, "redis returned empty body")
                    ngx.exit(500)
                  end

                  local parser = require "redis.parser"
                  local serveradr, typ = parser.parse_reply(res.body)

                  if typ ~= parser.BULK_REPLY or not serveradr then
                    ngx.log(ngx.ERR, "bad redis response: ", res.body)
                    ngx.exit(500)
                  end

                  server = serveradr cache:set(key, server,3600)                 end

                if server == "" then
                    server = "default"
                end

                server = server .. ngx.var.request_uri
                ngx.var.target = server
            ';

                resolver 255.255.255.0;
                proxy_pass http://$target;
         }
     }

 重點說一下上面的代碼:

 首先,定義了一個本地緩存:

lua_shared_dict localcache 10m;

 而後,從本地緩存中獲取路由地址

local cache = ngx.shared.localcache
local server = cache:get(key)

若是本地緩存中沒有,從Redis中獲取,從Redis中獲取到以後,加入到本地緩存中,緩存有效時間3600s:

server = serveradr
cache:set(key, server, 3600)

升級後的Redis+本地緩存的動態路由方案,壓測性能提高1.4倍,同時解決了Redis宕機的問題。
以上這個方案和實現都分享給你們。

 

周國慶

2017/11/13

相關文章
相關標籤/搜索