服务网关---基于Nginx+lua+Redis的服务降级设计(一)

一:服务限流功能点
    1:根据请求入参中的服务标识判断nginx后端服务是否处于流量限制中。如果是,则全部限制访问,否则,转发请求到后端服务。
    2:容错机制,如果Redis宕机等异常,限流模块失效,所有客户端请求放行。
    3:是否开启限流,及限流类型(AF:全部请求限制访问,PF:设置阈值,每秒限制请求多少次)可热加载。

二:设计思路
    1:在Reids中设置服务键值标识,Y标识限速,其它表示不限速。
    2:在Nginx中获取请求体,判断服务标识是否流量控制中,如果是,即拦截请求返回403,否则通过。
    3:如果在连接Redis和从Redis中读取数据时发生异常,则跳出限流模块,转发请求到服务端。

服务网关---基于Nginx+lua+Redis的服务降级设计(一)_第1张图片

三:Nginx配置及服务限流开关代码

http {
    include       mime.types;

    log_format  main  '$remote_addr - [$time_local] $request $status  $request_body';

    access_log  /home/lws/soft/openResty/nginx/logs/access.log main;

    upstream interServer{
    # ip_hash;  # 负载均衡算法,默认为轮询
    server 127.0.0.1:55555 max_fails=2 fail_timeout=2;
    server 127.0.0.1:55556 max_fails=2 fail_timeout=2;
    server 127.0.0.1:55557 max_fails=2 fail_timeout=2;
    }

    lua_package_path '/home/lws/soft/openResty/nginx/conf/lua/?.lua;;';

    lua_shared_dict ip_white_list 10m;  # 分配一块10m共享内存空间,缓存ip白名单
    lua_shared_dict limit_info 12m;     # 分配一块12m共享内存空间,缓存限流开关信息

    server {
        listen  8000;
        lua_need_request_body on;   # nginx默认不读取请求体,需要配置开启

        # 解决跨域问题
        # add_header Access-Control-Allow-Origin * always;
        # add_header Access-Control-Allow-Headers Origin,X-Requested-Width,Content-Type,Accept;

        location /rest/eai {
            # access_by_lua_file /home/lws/soft/openResty/nginx/conf/lua/ip_white_list.lua;
            access_by_lua_file /home/lws/soft/openResty/nginx/conf/lua/limit_rate.lua;
            proxy_pass http://interServer/rest/service;    # proxy_pass用来将请求反向代理到url参数指定的服务器

            proxy_redirect     off;
            # 修改从被代理服务器传来的应答头location和refresh字段
            # proxy_redirect [ default|off|redirect replacement ]
            # 默认值: proxy_redirect default
            # 使用字段:http, server, location
            # demo: proxy_redirect https://github.com  修改后的应答头;

            # 默认情况,反向代理不会转发原始请求中的 Host 头部,如果需要,使用proxy_set_header设置
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }

        # All forbidden
        # Partial forbidden
        # curl 'http://127.0.0.1:8000/set_limit_info?type=AF&value=Y'
        location /set_limit_info {
            content_by_lua '
                local limit_info = ngx.shared.limit_info
                local limit_type = ngx.var.arg_type
                local limit_value = ngx.var.arg_value
                if limit_type == "AF"  then
                    limit_info:set("limit_AF_flag",limit_value)
                    ngx.say(limit_type .. ":" .. limit_info:get("limit_AF_flag"))
                else
                    ngx.say("Your input incorrect")
                end
            ';
        }


    }

四:限流源码(AF:全部限制)

local mylib = require "do_mylib"
local cjson = require "cjson"

ngx.req.read_body()

local reqstr = ngx.req.get_body_data()

------- 请求报文可能为nil多考虑异常情况 让后将报文设置为NULL -------
if not reqstr then
    reqstr = ''
end

ngx.ctx.SRV_NAME = mylib.trim(mylib.get_xml_value(reqstr,'ActivityCode'))


-- 在此进行针对单个服务完全访问限制 调用本机 redis 的 6379 端口 进行控制
-- 需要在nginx.conf新增一个location,用于设置共享内存的标示,判断限速类型
-- 在redis中使用set设置键值,如 set 'T3000002' 'Y' 表示:T3000002的服务需要限速 Y表示限速,其它不限速
--[=[
		# All forbidden
        # Partial forbidden
        # curl 'http://127.0.0.1:8000/set_limit_info?type=AF&value=Y'
        location /set_limit_info {
            content_by_lua '
                local limit_info = ngx.shared.limit_info
                local limit_type = ngx.var.arg_type
                local limit_value = ngx.var.arg_value
                if limit_type == "AF"  then
                    limit_info:set("limit_AF_flag",limit_value)
                    ngx.say(limit_type .. ":" .. limit_info:get("limit_AF_flag"))
                else
                    ngx.say("Your input incorrect")
                end
            ';
        }
]=]

local limit_info = ngx.shared.limit_info
local rip = '127.0.0.1'
local rport = 6379

if limit_info:get('limit_AF_flag') == 'Y' then
    local key = string.format('%s',ngx.ctx.SRV_NAME)
    local retcode,retmsg = mylib.get_redis_info(rip,rport,key)
    if retcode == 0 then
    	if retmsg == 'Y' then
        	ngx.exit(ngx.HTTP_FORBIDDEN)
        end
    end
end

五:源码(自定义公共包)
 

--------- 定义库的函数 --------
module("do_mylib", package.seeall)

function get_xml_value(strxml,nodename)
  if strxml == '' or nodename == '' then
    return ''
  end
  local s1 = string.format('<%s>',nodename)
  local s2 = string.format('',nodename)
  local f1s,f1e = string.find(strxml,s1)
  if not f1s or not f1e then
    return ''
  end
  local f2s = string.find(strxml,s2,f1e)
  if f2s == nil then
    return ''
  end
  return string.sub(strxml,f1e+1,f2s-1)
end

---   字符串分割函数   ---
function split(inputstr, sep)
    local t={}
    local i=1
    for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
        t[i] = str
        i = i + 1
    end
    return t
end

function trim(s)
  local ss = s or ''
  return ss:match'^%s*(.*%S)' or ''
end


function get_redis_info(ip,port,key)
    local redis = require "resty.redis"
    local red = redis:new()
    local errmsg = ''
    red:set_timeout(1000) -- 1 sec
    local ok, err = red:connect(ip, port)
    if not ok then
        errmsg = string.format('MyRedisError:connect %s:%s:%s',ip,port,err)
        ngx.log(ngx.ERR, errmsg)
        return -1,errmsg
    end

    --添加redis的auth认证,如果无auth,则不需要
    local count
    count, err = red:get_reused_times()
    if 0 == count then
        ok, err = red:auth('liws')
        if not ok then
            errmsg = string.format("failed to auth %s",err)
            return -1, errmsg
        end
    elseif err then
        errmsg = string.format("failed to get reused times %s",err)
        return -1, errmsg
    end

    local res, err = red:get(key)
    if not res then
        errmsg = string.format('MyRedisError:get key:%s fail:%s:%s:%s',key,ip,port,err)
        ngx.log(ngx.ERR, errmsg)
        return -1,errmsg
    end
    -- put it into the connection pool of size 100,
    -- with 10 seconds max idle time
    local ok, err = red:set_keepalive(100000000, 128)
    if not ok then
        errmsg = string.format('MyRedisError:set_keepalive %s:%s:%s',ip,port,err)
        ngx.log(ngx.ERR, errmsg)
    end
    -- ngx.null  lua_resty_redis中查询为空返回ngx.null
    if res == ngx.null then
        errmsg = string.format('MyRedisError:key:%s not found %s:%s',key,ip,port)
        return -1, errmsg
    end
    return 0,res
end

 

你可能感兴趣的:(Nginx+Lua)