openresty 灰度发布 根据请求地址的参数转发至指定服务


公司业务需求 需要在新功能上线前给一部分用户作测试

网上查到 可以使用openresty 较为快速且侵入较小的实现

 

过程为不同用户浏览网站时, nginx获取到userId, 根据预先指定的userId转发至对应的服务器

在不重启nginx的情况下 可以动态指定服务地址给对应userId 做到动态添加灰度服务

 

 

conf/lua/redirect_by_user.lua

local _UTIL = {}

-- 获取请求当中的 userId
function _UTIL.get_user_by_req()
    -- header
    local headers = ngx.req.get_headers()
    local header_user_id = headers["userId"]
    if header_user_id ~=nil then
        return header_user_id
    end

    local req_method = ngx.var.request_method
     -- get
    if req_method == "GET" then
        local args = ngx.req.get_uri_args()
        return args["userId"]
    end
     -- post
    if req_method == "POST" then 
        -- 区分content-type
        local receive_headers = ngx.req.get_headers()
        local content_type = receive_headers["content-type"]

        if string.find(content_type, "application/json", 1, true) then
            ngx.req.read_body()
            local body_data = ngx.req.get_body_data()
            local cjson = require "cjson"
            local ok, json_data = pcall(cjson.decode, body_data)
            if ok then
                local jsusid = json_data["userId"]
                return jsusid
            end
            return nil
        end

        if string.find(content_type, "application/x-www-form-urlencoded", 1, true) then
            ngx.req.read_body()
            local post_args = ngx.req.get_post_args()
            return post_args["userId"]
        end
    end
    return nil;
end

local _M = {}
-- 连接redis 用于持久化灰度服务器地址(CANARY_MAP), 也可以不使用, 但nginx服务重启后 canary_map内容会失效
local redis_service = require "redis_service"

-- 用于缓存灰度的服务器地址,内容为{key: userId, value: 服务器ip/url}
local CANARY_MAP = nil

-- 初始化灰度服务的地址, 可删除, 删除后将 CANARY_MAP = nil 改为 CANARY_MAP = {} 否则报空异常
function init_canary_map()
    if CANARY_MAP == nil then
        CANARY_MAP = redis_service.get_canary_server()
    end
end

-- 获取转跳服务器地址 无则返回nil
function _M.redirect_for_canary()
    -- 初始化灰度服务地址 可删除
    init_canary_map()
    local version = ngx.var.cookie_version
   -- 保存cookie 用做快速判断
    if version == nil then
        -- 获取参数
        local user_id = _UTIL.get_user_by_req()
        if user_id then
            -- 保存标识 过期时间30分钟
            ngx.header.set_cookie = "version=" .. user_id .. "; path=/;  Expires=" .. ngx.cookie_time(ngx.time() + 60 * 30)
            local canary_server = CANARY_MAP[tostring(user_id)]
            if canary_server then
                return canary_server
            end
        end
        return nil
    end

    local canary_server = CANARY_MAP[tostring(version)]
    if canary_server then
        return canary_server
    else
        return nil
    end
end

-- 添加灰度服务器
function _M.add_canary()
    local args = ngx.req.get_uri_args()
    local user_id = args["uid"]
    local canary_val = args["val"]
    ngx.say("\n" .. user_id .. "|" .. canary_val .. "<< \n")
    if user_id and canary_val then 
        -- 初始化灰度服务地址 可删除
        init_canary_map()
        CANARY_MAP[tostring(user_id)] = canary_val
        -- 持久化 可删除
        redis_service.set_canary_server(CANARY_MAP)
        for k,v in pairs(CANARY_MAP) do
            ngx.say(k .. "|" .. v)
        end
        ngx.say("success")
        return
    end
    ngx.say("err, uid or val is nil")
end

return _M

 

conf/lua/redis_service.lua 用于灰度服务器记录的持久化 可以不使用

local _M = {}
-- 序列化 摘抄网上
function serialize(obj)
    local lua = ""
    local t = type(obj)
    if t == "number" then
        lua = lua .. obj
    elseif t == "boolean" then
        lua = lua .. tostring(obj)
    elseif t == "string" then
        lua = lua .. string.format("%q", obj)
    elseif t == "table" then
        lua = lua .. "{"
    for k, v in pairs(obj) do
        lua = lua .. "[" .. serialize(k) .. "]=" .. serialize(v) .. ","
    end
    local metatable = getmetatable(obj)
        if metatable ~= nil and type(metatable.__index) == "table" then
        for k, v in pairs(metatable.__index) do
            lua = lua .. "[" .. serialize(k) .. "]=" .. serialize(v) .. ","
        end
    end
        lua = lua .. "}"
    elseif t == "nil" then
        return nil
    else
        ngx.log(ngx.ERR, "can not serialize a " .. t .. " type.")
    end
    return lua
end

-- 反序列化 摘抄网上
function unserialize(lua)
    local t = type(lua)
    if t == "nil" or lua == "" then
        return nil
    elseif t == "number" or t == "string" or t == "boolean" then
        lua = tostring(lua)
    else
        ngx.log(ngx.ERR, "can not unserialize a " .. t .. " type.")
    end
    lua = "return " .. lua
    local func = loadstring(lua)
    if func == nil then
        return nil
    end
    return func()
end

-- redis连接 需要close
function redis_content()
    local redis_c = require "resty.redis"
    local red = redis_c:new()
    red:set_timeout(3000)
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        return nil
    end
    return red
end

function _M.get_canary_server()
    local red = redis_content()
    if red == nil then
        return {}
    end
    local res, err = red:get("server_map")
    red:close()
    if res ~= nil and res ~= null and res ~= ngx.null then
        return unserialize(res)
    end
    return {}
end

function _M.set_canary_server(val)
    local red = redis_content()
    if red == nil then
        return
    end
    local res, err = red:set("server_map", serialize(val))
    red:close()
end

return _M

 

conf/nginx.conf 配置

worker_processes  1;
error_log logs/error.log;
events {
    worker_connections 1024;
}


http {
    # 热加载
    # lua_code_cache off;
    lua_package_path '$prefix/conf/lua/?.lua;;';

    # 默认服务器
    upstream default_ups {
        server 127.0.0.1:8080;
    }
    
    server {
        listen 10001;
        
        location / {
            # include	proxy-options.conf;
            set         $curl_val          'default_ups';
            rewrite_by_lua ' 
                local fun = require "redirect_by_user"
                local cur_val = fun.redirect_for_canary()
                if cur_val then
                     ngx.var.curl_val = cur_val
                end
            ';
            proxy_pass  http://$curl_val;
        }
    }

    server {
        listen 10002;
        # 例
        # curl -s http://127.0.0.1:10002/add?uid=1\&val=127.0.0.1:8081
        # 添加灰度服务器
        location /add {
            include	proxy-options.conf;

            content_by_lua '
                local fun = require "redirect_by_user"
                fun.add_canary()
            ';
        }
    }

}

 

安装openresty后(网上可找到教程), 建一个目录存放以上文件, 启动命令

openresty -p `pwd`/ -c conf/nginx.conf

 

 

写过程中还遇到resty.redis的一些坑

原本在ngxin.conf中配置init_by_lua 使canary_map初始化, 但init_by_lua中无法使用resty.redis包 会报异常(详见: https://github.com/openresty/lua-nginx-module/issues/206)

使用resty.redis 获取redis中的值时(res:get()方法), 假如返回为空, 不是lua中的nil, 而是null,  所以用↓判断(可参考: https://github.com/openresty/lua-resty-redis/issues/90)

    if res ~= nil and res ~= null and res ~= ngx.null then
        return nil
    end

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM