基于abtest思想的流量切换(nginx lua redis)

3 篇文章 0 订阅

使用前提:

项目重构了,旧项目还在线上运行,新项目准备替换线上的旧项目

最终目标:

要实现实时切换新旧项目,保证如果新项目上线后有问题,可以立刻快速的将流量切回旧项目

方案:

关于abtest的基本原理本文不再多说,本文重点是实践,先看图
在这里插入图片描述
如上图所示,用户访问的永远都是dns,单独集群部署,由dns上的配置决定后面的访问的集群
旧项目nginx和旧项目tomcat在一组linux上部署
新项目nginx和新项目tomcat在一另组linux上部署

只有旧项目的时候,就是dns直接打到旧项目nginx
升级新项目后,需要将新项目无感知的上线,并换下旧项目

第一步是嵌入新项目nginx,dns将流量打到新项目nginx,再转到旧项目nginx,
稳定后再下掉dns打到旧项目nginx的流量,最终结果就是图中第一步所示

第二步是使用lua模块引入外部redis,在nginx中配置,将新项目nginx的流量可配置的转到新项目tomcat

第三步是备用步骤,如果切到新项目后,发现线上有问题,就可以通过操作redis来控制新项目nginx的负载分配,可以达到几秒内迅速切回旧项目

有人会问,为什么不直接在dns这一层来做负载分配,其实也是可以的,只不过对于大的公司来说,这一层普通开发一般没有操作权限,即使可以通过一些配置完成,其中也经过了很多转换,导致切换一次所耗费的时间达到一分钟以上,而本次想实现的目标是快速切流量,所以用了本地的nginx

具体实现

新项目的nginx–config核心逻辑:

#首先在机器上要安装lua模块,来支持lua语言
lua_package_path "/export/servers/lualib/?.lua;;";
lua_package_cpath "/export/servers/lualib/?.so;;";
resolver 172.16.16.16  10.16.16.16;

#这里加载了init.lua和worker.lua
init_by_lua_file        /export/Packages/新项目名/latest/WEB-INF/classes/conf/abtesting/init.lua;
init_worker_by_lua_file   /export/Packages/新项目名/latest/WEB-INF/classes/conf/abtesting/worker.lua;

#设置新项目
upstream tomcat_mytomcat01 {
    server 127.0.0.1:1601  weight=10 max_fails=2 fail_timeout=30s ;
}
#设置旧项目01
upstream tomcat_oldtomcat01 {
    server XX.XXX.XXX.XX1:80  weight=10 max_fails=2 fail_timeout=30s ;
    server XX.XXX.XXX.XX2:80  weight=10 max_fails=2 fail_timeout=30s ;
}
#设置旧项目02(原来是两个项目,重构后合成一个项目,所以要有旧项目02)
upstream tomcat_oldtomcat02  {
	server XX.XXX.XXX.XX3:80  weight=10 max_fails=2 fail_timeout=30s ;
	server XX.XXX.XXX.XX4:80  weight=10 max_fails=2 fail_timeout=30s ;
}

#nginx日志格式
log_format newmain         	'$remote_addr - "$http_x_forwarded_for" - $remote_user [$time_local]'
                                        	        '"$request" $status $bytes_sent '
                                                	'"$http_referer" "$http_user_agent" '
                                             	        '"$gzip_ratio" - "$http_x_proto" - "$host" ';
server {
    listen          80;
    server_name     *.*.com ;
    access_log      /export/servers/nginx/logs/otcfront.jd.com/otcfront.jd.com_access.log newmain;
    error_log       /export/servers/nginx/logs/otcfront.jd.com/otcfront.jd.com_error.log warn;

    root /export/Packages/项目名/latest/;
    
    #默认流量打在新项目
    set $default_backend 'tomcat_mytomcat01';
    
    location / {
        proxy_next_upstream     http_500 http_502 http_503 http_504 error timeout invalid_header;
        proxy_set_header        Host  $host;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        
        expires                 0;
        
        #首先将默认值给backend,proxy_pass最终会以backend的值为准
        set $backend $default_backend;
        
		#匹配host,判断是否修改backend的值,以(www.zhangs01.com为例,是旧项目01对应的域名)
        if ($host ~* ^www\.zhangs01\.com$){
	        #先默认将流量打到旧项目上
           set $backend "tomcat_oldtomcat01 ";
           #如果读出来在diversion01.lua中有对$backend的值做修改,则使用新的值
        	 rewrite_by_lua_file '/export/Packages/新项目名/latest/WEB-INF/classes/conf/abtesting/diversion01.lua';
        }
        
        #匹配host,判断是否修改backend的值,以(www.zhangs02.com为例,是旧项目02对应的域名)
        if ($host ~* ^www\.zhangs02\.com$){
	         #先默认将流量打到旧项目上
        	 set $backend "tomcat_oldtomcat02 ";
        	 #如果读出来在diversion02.lua中有对$backend的值做修改,则使用新的值
        	 rewrite_by_lua_file '/export/Packages/新项目名/latest/WEB-INF/classes/conf/abtesting/diversion02.lua';
        }
        
        #最终打到backend对应的地方
        proxy_pass            http://$backend;
    }

    location /logs/ {
        autoindex       off;
        deny all;
    }
}

上面一段nginx中涉及到四个lua文件
init.lua—> 初始化参数
worker.lua—> 真正的分流逻辑
diversion01.lua—> 旧项目01的 b a c k e n d 设 置 d i v e r s i o n 02. l u a − − − > 旧 项 目 02 的 backend设置 diversion02.lua---> 旧项目02的 backenddiversion02.lua>02backend设置

init.lua核心代码:
--定义全局变量
global_configs = {
   --在diversion01.lua中会用到这个值
  ["divEnable01"] = false,
  --在diversion02.lua中会用到这个值
  ["divEnable02"] = false,
  --连接redis的必要参数
  ["redis"] = {
    ap_host='XXX.XX.XXX',
    ap_port=XXXX,
    ap_key='/redis/XXXXXXXXXXXX(redis地址)'
  }
}
worker.lua核心代码:
--初始化延迟时间,10秒
local start_delay = 10
--定义ngx.timer.at指令,这个指令中可以设置回调函数,回调函数中再执行这个指令,就可以循环起来
local new_timer = ngx.timer.at
local log = ngx.log
local ERR = ngx.ERR
local refresh
local get_redis
local close_redis

--初始化两个redis的key,对应的value值是true就代表切到新项目,false就代表切到旧项目
local switch_key_01 = "abtest:switch:global01"
local switch_key_02 = "abtest:switch:global02"

--定义获取redis方法
get_redis = function()
  local redis = require "resty.redis"
  local red = redis:new()
  local ok, err = red:connect(global_configs['redis']['ap_host'],global_configs['redis']['ap_port'])
  if ok and global_configs['redis']['ap_key'] then
    ok, err = red:auth(global_configs['redis']['ap_key'])
  end
  return red, ok, err
end

--定义关闭redis连接方法
close_redis = function(red)
        if not red then
        return
    end
    local ok, err = red:close()
    if not ok then
        ngx.log(ngx.ERR,"fail to close redis connection : ", err)
    end
end

--核心逻辑
local function do_refresh()
	--获取redis
    local red, ok, err = get_redis()
	
	--判活
    if not ok then
        log(ERR, "redis is not ready!")
        return
    end

    -- refresh global switch01
    --获取key为"switch_key_01"的value值,用变量enable01保存
    local enable01, err = red:get(switch_key_01)
    if err then
        log(ERR, err)
    else
	    
        if ngx.null ~= enable01 then
	        --如果enable01 不为null,并且enable01的值是"true",就将全局变量global_configs["divEnable01"]的值设置成true,否则就是false
            global_configs["divEnable01"] = ("true" == enable01) and true or false
        end
    end

    -- refresh global switch02,同理01
    local enable02, err = red:get(switch_key_02)
    if err then
        log(ERR, err)
    else
        if ngx.null ~= enableTrade then
            global_configs["divEnable02"] = ("true" == enable02) and true or false
        end
    end

    return close_redis(red)
end

--刷新方法,这里当成一个回调函数来用,被后面的new_timer调用
refresh = function(premature)
    if not premature then
        log(ERR, "rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrefresh")
        --调用核心逻辑(从redis中取key,判断value的值,从而确定流量分给谁)
        do_refresh()

		--再次调用这个new_time,构成持续循环
       local ok, e = new_timer(start_delay, refresh)
        if not ok then
            log(ERR, "failed to create timer: ", e)
            return
        end
    end
end


--第一次调用这里,10秒后调用上面的回调函数
local ok, e = new_timer(start_delay, refresh)
if not ok then
    log(ERR, "failed to create timer: ", e)
    return
end
diversion01.lua核心代码
--如果init.lua中的全局变量global_configs["divEnable01"]是false,就直接返回
if not global_configs["divEnable01"] then
	return
end

--如果init.lua中的全局变量global_configs["divEnable01"]是true,就将backend 的值设置成tomcat_mytomcat01
--tomcat_mytomcat01 是最一开始在nginx的配置文件中调用的
ngx.var.backend = "tomcat_mytomcat01" 

diversion02.lua核心代码
--如果init.lua中的全局变量global_configs["divEnable02"]是false,就直接返回
if not global_configs["divEnable02"] then
	return
end

--如果init.lua中的全局变量global_configs["divEnable02"]是true,就将backend 的值设置成tomcat_mytomcat01
--tomcat_mytomcat01 是最一开始在nginx的配置文件中调用的
ngx.var.backend = "tomcat_mytomcat01" 

最后捋一遍:
首先在nginx中加载init.lua,初始化几个全局变量
再加载worker.lua,使用lua回调函数实现循环,实时读取redis中的key的值
根据redis中的值的变化来改变nginx最终负载指向的位置,从而实现实时的控制流量方向

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Docker是一种容器化技术,它可以将应用程序及其依赖项打包成容器,提供了快速、轻量级和可移植的环境。Nginx是一个高性能的Web服务器和反向代理服务器,它可以处理大量的并发连接。Lua是一种轻量级的脚本语言,可以嵌入到Nginx中,用于定制化和扩展性能。Redis是一个高性能的内存键值存储系统,它支持多种数据结构,并提供了持久化功能。 将这些技术结合起来,可以构建一个高性能、可扩展和可靠的Web应用系统。首先,我们可以使用Docker来创建一个包含NginxLuaRedis的容器环境。Nginx可以作为Web服务器,将用户请求转发到不同的后端服务,并通过使用Lua脚本来增加自定义的功能和处理逻辑。Redis可以作为Nginx的缓存数据库,存储一些频繁使用的数据,以提高系统的响应速度和性能。 使用Docker可以快速部署整个系统,并且容器之间相互隔离,避免了环境依赖和冲突的问题。同时,通过Docker的容器编排工具,我们可以管理和扩展应用程序的实例数量,以满足流量的需求。此外,Docker还能够自动完成部署、升级和回滚等操作,提高了系统的可靠性和可维护性。 总的来说,使用Docker、NginxLuaRedis可以搭建一个高性能、可伸缩和可靠的Web应用系统。这个系统能够提供快速的响应速度、高并发处理能力,并且具备良好的可扩展性和可维护性。同时,通过使用容器化技术,我们可以更简单地部署和管理整个应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值