百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Openresty灰度发布及版本约束(openresty 灰度)

off999 2025-04-11 04:32 37 浏览 0 评论

目标

利用openresty配合Lua脚本实现基于redis配置进行灰度发布及最小版本约束。实现如下功能:
1、随机灰度
2、基于用户ID灰度(用户ID%100<Radio)
3、基于指定用户ID灰度(例如用户ID:2、3)
4、基于App版本号进行灰度(例如内部版本号:31)
5、全量灰度
6、App最小版本约束

代码约束

1、App请求头中携带客户端类型(X-App-Type)、客户端版本号(X-App-Version)
2、App请求头中携带Token信息(X-Client-Token),用于获取用户ID(测试脚本中token生成规则为 userid_token),实际使用中,需要根据token机制进行用户ID转换(修改getUserId方法)
3、代码中自动忽略了版本号为空或为0的情况,如果需要判断,需要修改分发逻辑
4、通过修改send_upgrade方法,自行配置版本过低的提示
5、客户端版本号为数字
6、客户端类型为数字;代码中100:代表ios 200:代表android

Redis 配置参考(gray.config)

{
    "ratio": "30",
    "minVersion": "1",
    "versions": [
        "10"
    ],
    "type": 1,
    "gray": "192.168.1.2:8080",
    "default": "192.168.1.2:8081",
    "userIds": [
        "1"
    ]
}

Nginx 全局配置

 增加如下代码
....
http{
  ....
  lua_code_cache on;
  lua_shared_dict gray_cache 10m;
  ....
}

Nginx 转发配置

server {
   listen       80;
   location / {
     set $target '';
     default_type text/html;
     proxy_set_header    X-Real-IP $remote_addr;
     proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
     access_by_lua_file /etc/nginx/lua/gray.lua;
     proxy_pass http://$target$request_uri;
   }
}

Lua脚本

local redis = require "resty.redis";
local cjson = require("cjson")
local function isEmpty(s)
    return s == nil or s == ''
end
local function stringToInt(str)
    if isEmpty(str) then
        return 0
    end
    local number = tonumber(str)
    if not number then
        return 0
    end
    return number
end
local function left(str, split)
    local index = string.find(str, split)
    if not index then
        return nil
    end
    local result = string.sub(str, 0, index - 1)
    return result
end
local function getUserId()
    -- 根据token计算用户ID,需根据自己的业务就行替换
    local token = ngx.req.get_headers()["X-Client-Token"]
    if isEmpty(token) then
        return 0
    end
    local uidStr = left(token, "_")
    if isEmpty(uidStr) then
        return 0
    end
    return stringToInt(uidStr)
end
local function getClientVersion()
    local version = ngx.req.get_headers()["X-App-Version"]
    return stringToInt(version)
end
local function getClientType()
    -- 客户端类型,在这个地方 100表示ios 200表示 安卓
    local version = ngx.req.get_headers()["X-App-Type"]
    return stringToInt(version)
end
local function close_redis(redis_cluster)
    if not redis_cluster then
        return
    end
    local pool_max_idle_time = 10000
    local pool_size = 100
    local ok, err = redis_cluster:set_keepalive(pool_max_idle_time, pool_size)
    if not ok then
        ngx.log(ngx.ERR, "set keepalive fail ", err)
    end
end
local function read_gray_config(address, port, password, key, default_config)
    local redis_cache = redis:new();
    redis_cache:set_timeout(1000);
    local ok, err = redis_cache:connect(address, port);
    if not ok then
        close_redis(redis_cache)
        ngx.log(ngx.ERR, "redis 连接错误: ", err)
        return default_config;
    end
    if not isEmpty(password) then
        local ok, err = redis_cache:auth(password)
        if not ok then
            ngx.log(ngx.ERR, "redis 携带密码连接错误: ", err)
            close_redis(redis_cache)
            return default_config;
        end
    end
    local res, err = redis_cache:get(key)
    if not res then
        ngx.log(ngx.ERR, "redis读取数据错误: ", err)
        close_redis(redis_cache)
        return default_config
    end
    local json = cjson.new()
    if not json then
        ngx.log(ngx.ERR, "创建json错误 ")
        close_redis(redis_cache)
        return default_config
    end
    if res == ngx.null then
        local ok, err = redis_cache:set(key, json.encode(default_config))
        if not ok then
            ngx.log(ngx.ERR, "写入默认配置出错: ", err)
        end
        ngx.log(ngx.INFO, "灰度配置为空,采用默认配置")
        close_redis(redis_cache)
        return default_config
    else
        close_redis(redis_cache)
        return json.decode(res)
    end
end
local function load_gray(address, port, password, timeout, key, default_config)
    local share_cache = ngx.shared.gray_cache
    local cache_data = share_cache:get("config")
    local json = cjson.new()
    if not json then
        ngx.log(ngx.ERR, "创建json对象错误 ")
        return default_config
    end
    if cache_data == nil then
        cache_data = read_gray_config(address, port, password, key, default_config)
        if cache_data == nil then
            ngx.log(ngx.ERR, "获取配置信息返回null")
        else
            local ok, err = share_cache:set("config", json.encode(cache_data), timeout)
            if not ok then
                ngx.log(ngx.INFO, "刷新本地灰度配置信息失败", err)
            else
                ngx.log(ngx.INFO, "刷新本地灰度配置信息成功")
            end
        end
        return cache_data
    else
        ngx.log(ngx.INFO, "采用缓存配置信息")
        return json.decode(cache_data)
    end
end
local default_cache = {
    type = 0, -- 灰度类型 0、关闭灰度 1、随机灰度 2、根据用户ID灰度 3、指定用户ID灰度 4、指定用户版本灰度 5、全量灰度
    default = "192.168.1.2:8080", -- 正常分发地址
    gray = "192.168.1.2:8081", -- 灰度分发地址
    userIds = { "0" }, -- 灰度用户ID,例如:{"2","3","4"}
    ratio = "0", -- 灰度分发比例
    minVersion = "0", -- 客户端最小版本号
    versions = { "0" } -- 灰度版本号,例如:{"30","31"}
}
local function contains(value, list)
    if list == nil or isEmpty(value) then
        return false
    end
    for k, v in ipairs(list) do
        if v == value then
            return true;
        end
    end
    return false;
end
-- 发送版本过低消息
local function send_upgrade(minVersion,clientType)
    local upgrade_response = '{"code":403,"data":{"version":"0","message":"您当前的版本过低,请升级到最新版本!"}}'
    ngx.header.content_type = "application/json"
    ngx.say(string.format(upgrade_response,clientType,minVersion,minVersion))
end
local gray = load_gray("127.0.0.1", 6379, "", 10, "gray.config", default_cache)
if gray then
    ngx.var.target = gray["default"]
    local gray_type = gray["type"]
    local iosMinVersion = gray["iosMinVersion"]
    local andoridMinVersion = gray["andoridMinVersion"]
    local clientType = getClientType()
    local request_uri = ngx.var.request_uri
    if (string.find(request_uri, "^/yuliao/uri/") == nil) then
        local clientVersion = getClientVersion()
        if clientType == 100 and iosMinVersion ~= nil and iosMinVersion > 0 then
            -- 判断ios最小版本
            local clientVersion = getClientVersion()
            if clientVersion > 0 and clientVersion < iosminversion then ngx.logngx.infouri:request_uri send ios upgrade response send_upgradeiosminversionclienttype return end else if clienttype='= 200' and andoridminversion and andoridminversion> 0 then
            -- 判断安卓最小版本
            if clientVersion > 0 and clientVersion < andoridMinVersion and clientVersion ~= 1 then
                ngx.log(ngx.INFO,"uri:",request_uri," send android upgrade response")
                send_upgrade(andoridMinVersion,clientType)
                return;
            end
        end
    end
    end
    if gray_type == 1 then
        -- 随机灰度
        local ratio = stringToInt(gray["ratio"])
        local number = math.random(100) % 100
        if number < ratio then
            ngx.var.target = gray["gray"]
            ngx.log(ngx.INFO, "随机灰度(YES):", " number:", number, " ratio:", ratio, " upstream:", ngx.var.target)
        else
            ngx.log(ngx.INFO, "随机灰度(NO):", " number:", number, " ratio:", ratio, " upstream:", ngx.var.target)
        end
    elseif gray_type == 2 then
        -- 用户ID灰度
        local ratio = stringToInt(gray["ratio"])
        local userId = getUserId()
        local number = userId % 100
        if number < ratio then ngx.var.target='gray["gray"]' ngx.logngx.info idyes: userid: userid ratio: ratio upstream: ngx.var.target else ngx.logngx.info idno: userid: userid ratio: ratio upstream: ngx.var.target end elseif gray_type='= 3' then -- id local userid='getUserId()' if userid> 0 then
            userId = tostring(userId)
            local userIds = gray["userIds"]
            if contains(userId, userIds) then
                ngx.var.target = gray["gray"]
                ngx.log(ngx.INFO, "指定用户灰度(YES):", " userId:", userId, " upstream:", ngx.var.target)
            else
                ngx.log(ngx.INFO, "指定用户灰度(NO):", " userId:", userId, " upstream:", ngx.var.target)
            end
        else
            ngx.log(ngx.INFO, "指定用户灰度(NO):", " userId:", userId, " upstream:", ngx.var.target)
        end
    elseif gray_type == 4 then
        -- 指定用户版本灰度
        if version > 0 then
            local versions = gray["versions"]
            version = tostring(version)
            if contains(version, versions) then
                ngx.var.target = gray["gray"]
                ngx.log(ngx.INFO, "指定版本灰度(YES):", " version:", version, " upstream:", ngx.var.target)
            else
                ngx.log(ngx.INFO, "指定版本灰度(NO):", " version:", version, " upstream:", ngx.var.target)
            end
        else
            ngx.log(ngx.INFO, "指定版本灰度(NO):", " version:", version, " upstream:", ngx.var.target)
        end
    elseif gray_type == 5 then
        ngx.var.target = gray["gray"]
        ngx.log(ngx.INFO, "系统全量灰度(YES):", " upstream:", ngx.var.target)
    end
else
    local json = cjson.new()
    ngx.header.content_type = "application/json"
    ngx.say(cjson.encode({ code = 500, message = '无法找到转发配置,请联系管理员!' }))
    ngx.log(ngx.ERR, "无法找到系统配信息,返回500")
end

相关推荐

大文件传不动?WinRAR/7-Zip 入门到高手,这 5 个技巧让你效率翻倍

“这200张照片怎么传给女儿?微信发不了,邮箱附件又超限……”62岁的张阿姨对着电脑犯愁时,儿子只用了3分钟就把照片压缩成一个文件,还教她:“以后用压缩软件,比打包行李还方便!”职场人更懂这...

电脑解压缩软件推荐——7-Zip:免费、高效、简洁的文件管理神器

在日常工作中,我们经常需要处理压缩文件。无论是下载软件包、接收文件,还是存储大量数据,压缩和解压缩文件都成为了我们日常操作的一部分。而说到压缩解压软件,7-Zip绝对是一个不可忽视的名字。今天,我就来...

设置了加密密码zip文件要如何打开?这几个方法可以试试~

Zip是一种常见的压缩格式文件,文件还可以设置密码保护。那设置了密码的Zip文件要如何打开呢?不清楚的小伙伴一起来看看吧。当我们知道密码想要打开带密码的Zip文件,我们需要用到适用于Zip格式的解压缩...

大文件想要传输成功,怎么把ZIP文件分卷压缩

不知道各位小伙伴有没有这样的烦恼,发送很大很大的压缩包会受到限制,为此,想要在压缩过程中将文件拆分为几个压缩包并且同时为所有压缩包设置加密应该如何设置?方法一:使用7-Zip免费且强大的文件管理工具7...

高效处理 RAR 分卷压缩包:合并解压操作全攻略

在文件传输和存储过程中,当遇到大文件时,我们常常会使用分卷压缩的方式将其拆分成多个较小的压缩包,方便存储和传输。RAR作为一种常见的压缩格式,分卷压缩包的使用频率也很高。但很多人在拿到RAR分卷...

2个方法教你如何删除ZIP压缩包密码

zip压缩包设置了加密密码,每次解压文件都需要输入密码才能够顺利解压出文件,当压缩包文件不再需要加密的时候,大家肯定想删除压缩包密码,或是忘记了压缩包密码,想要通过删除操作将压缩包密码删除,就能够顺利...

速转!漏洞预警丨压缩软件Winrar目录穿越漏洞

WinRAR是一款功能强大的压缩包管理器,它是档案工具RAR在Windows环境下的图形界面。该软件可用于备份数据,缩减电子邮件附件的大小,解压缩从Internet上下载的RAR、ZIP及其它类...

文件解压方法和工具分享_文件解压工具下载

压缩文件减少文件大小,降低文件失效的概率,总得来说好处很多。所以很多文件我们下载下来都是压缩软件,很多小伙伴不知道怎么解压,或者不知道什么工具更好,所以今天做了文件解压方法和工具的分享给大家。一、解压...

[python]《Python编程快速上手:让繁琐工作自动化》学习笔记3

1.组织文件笔记(第9章)(代码下载)1.1文件与文件路径通过importshutil调用shutil模块操作目录,shutil模块能够在Python程序中实现文件复制、移动、改名和删除;同时...

Python内置tarfile模块:读写 tar 归档文件详解

一、学习目标1.1学习目标掌握Python内置模块tarfile的核心功能,包括:理解tar归档文件的原理与常见压缩格式(gzip/bz2/lzma)掌握tar文件的读写操作(创建、解压、查看、过滤...

使用python展开tar包_python拓展

类Unix的系统,打包文件经常使用的就是tar包,结合zip工具,可以方便的打包并解压。在python的标准库里面有tarfile库,可以方便实现生成了展开tar包。使用这个库最大的好处,可能就在于不...

银狐钓鱼再升级:白文件脚本化实现GO语言后门持久驻留

近期,火绒威胁情报中心监测到一批相对更为活跃的“银狐”系列变种木马。火绒安全工程师第一时间获取样本并进行分析。分析发现,该样本通过阿里云存储桶下发恶意文件,采用AppDomainManager进行白利...

ZIP文件怎么打开?2个简单方法教你轻松搞定!

在日常工作和生活中,我们经常会遇到各种压缩文件,其中最常见的格式之一就是ZIP。ZIP文件通过压缩数据来减少文件大小,方便我们进行存储和传输。然而,对于初学者来说,如何打开ZIP文件可能会成为一个小小...

Ubuntu—解压多个zip压缩文件.zip .z01 .z02

方法将所有zip文件放在同一目录中:zip_file.z01,zip_file.z02,zip_file.z03,...,zip_file.zip。在Zip3.0版本及以上,使用下列命令:将所有zi...

如何使用7-Zip对文件进行加密压缩

7-Zip是一款开源的文件归档工具,支持多种压缩格式,并提供了对压缩文件进行加密的功能。使用7-Zip可以轻松创建和解压.7z、.zip等格式的压缩文件,并且可以通过设置密码来保护压缩包中的...

取消回复欢迎 发表评论: