• Nginx技术研究系列5-动态路由升级版


    前几篇文章我们介绍了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;
                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

  • 相关阅读:
    Java的常用API之System类简介
    Java的常用API之Date类简介
    Java的常用API之Object类简介
    数据库知识总结(全)
    学习:浏览器访问网站的总流程
    学习:TCP/UDP协议分析(TCP四次挥手)
    学习:TCP/UDP协议分析(TCP三次握手)
    学习:ICMP协议
    实现:ARP探测存活主机
    学习:ARP协议/数据包分析
  • 原文地址:https://www.cnblogs.com/tianqing/p/7821285.html
Copyright © 2020-2023  润新知