Nginx-動態路由升級版
前幾篇文章我們介紹了Nginx的配置、OpenResty安裝配置、基於Redis的動態路由以及Nginx的監控。
Nginx-OpenResty安裝配置
Nginx配置詳解
Nginx技術研究系列1-通過應用場景看Nginx的反向代理
Nginx技術研究系列2-基於Redis實現動態路由
[原創]Nginx監控-Nginx+Telegraf+Influxb+Grafana
在分布式環境下,我們要考慮高可用性和性能:
1. 不能因為Redis宕機影響請求的反向代理
2. 每次請求如果都訪問Redis,性能有一定的損耗,同時Redis集群的壓力隨著流量的激增不斷增加。
因此,我們要升級一下動態路由的設計方案。 我們還是先從應用場景出發:
1.提升性能,降低Redis的訪問頻率
2.Redis宕機不影響Nginx反向代理
實現上述兩個應用場景,我們采用本地緩存+Redis緩存的雙緩存配合機制。
- 第一次請求時,從Redis中獲取路由地址,然後放到本地緩存中,同時設置本地緩存項的有效時間
- 後續請求時,從本地緩存直接獲取路由地址,如果本地緩存已經失效,則再次從Redis獲取路由地址,再放到本地緩存中。
確定了上述實現方案後,我們回顧一下我們已有的Redis動態路由實現:
upstream redis_cluster { server 192.0.1.*:6379; server192.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
Nginx-動態路由升級版