在我们的业务版本迭代中,经常会出现新版本需要灰度上线,仅先用于部分门店(SAAS点餐系统),导致生产环境需要同时运行多个版本代码,一开始我们的临时处理方案是:在NGINX项目配置文件中,通过判断客户端上传的HEADER信息中的版本号,改变ROOT值,解析到相应的代码分支。这种方式每出一个新包都要手动做一次配置文件的修改,十分不便。
后来自己将服务器上的NGINX换成了Openresty(这个过程并不复杂,对已有业务也没影响),然后写了一个LUA脚本,可以通过后台去配置门店所需要使用的分支,并且按照设备号对应出的分支缓存到REDIS,后台再增加一个按设备号(支持批量)清缓存的功能,便实现了代码灰度发布的自由配置。
相关截图一(后台设置门店对应的接口分支名称):
相关截图二(查看设备号对应的分支名称及清除缓存按钮)
最后是改变ROOT的LUA代码:
local config = require "config" local g = require "lib.g" local redis = require "lib.redis" local mysql = require "lib.mysql" local header = ngx.req.get_headers() local device_no = header.deviceno local store_id = header.storeid if(device_no == nil and store_id == nil) then g.log("empty device_no and store_id") return end local redis = redis:new(config:get("redis")) local key,path if(device_no ~= nil) --设备号-- then key = "lua|device_no|api_path" path = redis:hget(key, device_no) else --门店ID-- key = "lua|store_id|api_path" path = redis:hget(key, store_id) end if(path == 'default') then return end if not g.empty(path) then ngx.var.branch = path return path end if(device_no ~= nil) --设备号-- then redis:hset(key, device_no, 'default') g.log("no lua cache|device_no:" .. device_no) else --门店ID-- redis:hset(key, store_id, 'default') g.log("no lua cache|store_id:" .. store_id) end local db_config = config:get("mysql") local db_base_config = db_config.db_base local db_base = mysql:new(db_base_config) local device_info if(device_no ~= nil) --设备号-- then --通过设备号获取门店ID local sql = "select store_id from wt_device where device_no=:device_no" local bind = { device_no = device_no } device_info = db_base:findOne(sql, bind) if g.empty(device_info) then g.log("empty device_info") return end if g.empty(device_info.store_id) then g.log("empty device_info.store_id") return end end --通过门店ID获取路径配置 local sql = "select path_name from wt_store_config where store_id=:store_id" local bind if(device_no ~= nil) --设备号-- then bind = { store_id = device_info.store_id } else --门店ID-- bind = { store_id = store_id } end local store_config = db_base:findOne(sql, bind) if g.empty(store_config) then g.log("empty store_config") return end if g.empty(store_config.path_name) then g.log("empty store_config.path_name") return end if(device_no ~= nil) --设备号-- then redis:hset(key, device_no, store_config.path_name) else --门店ID-- redis:hset(key, store_id, store_config.path_name) end ngx.var.branch = store_config.path_name return