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

使用前提:

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

最终目标:

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

方案:

关于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的backend设置diversion02.lua−−−>旧项目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'] thenok, err = red:auth(global_configs['redis']['ap_key'])endreturn red, ok, err
end--定义关闭redis连接方法
close_redis = function(red)if not red thenreturnendlocal ok, err = red:close()if not ok thenngx.log(ngx.ERR,"fail to close redis connection : ", err)end
end--核心逻辑
local function do_refresh()--获取redislocal red, ok, err = get_redis()--判活if not ok thenlog(ERR, "redis is not ready!")returnend-- refresh global switch01--获取key为"switch_key_01"的value值,用变量enable01保存local enable01, err = red:get(switch_key_01)if err thenlog(ERR, err)elseif ngx.null ~= enable01 then--如果enable01 不为null,并且enable01的值是"true",就将全局变量global_configs["divEnable01"]的值设置成true,否则就是falseglobal_configs["divEnable01"] = ("true" == enable01) and true or falseendend-- refresh global switch02,同理01local enable02, err = red:get(switch_key_02)if err thenlog(ERR, err)elseif ngx.null ~= enableTrade thenglobal_configs["divEnable02"] = ("true" == enable02) and true or falseendendreturn close_redis(red)
end--刷新方法,这里当成一个回调函数来用,被后面的new_timer调用
refresh = function(premature)if not premature thenlog(ERR, "rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrefresh")--调用核心逻辑(从redis中取key,判断value的值,从而确定流量分给谁)do_refresh()--再次调用这个new_time,构成持续循环local ok, e = new_timer(start_delay, refresh)if not ok thenlog(ERR, "failed to create timer: ", e)returnendend
end--第一次调用这里,10秒后调用上面的回调函数
local ok, e = new_timer(start_delay, refresh)
if not ok thenlog(ERR, "failed to create timer: ", e)return
end

diversion01.lua核心代码

--如果init.lua中的全局变量global_configs["divEnable01"]是false,就直接返回
if not global_configs["divEnable01"] thenreturn
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"] thenreturn
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最终负载指向的位置,从而实现实时的控制流量方向

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500003.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript内部实现

前言 JavaScript 的核心 ECMAScript 描述了该语言的语法和基本对象; DOM 描述了处理网页内容的方法和接口; BOM 描述了与浏览器进行交互的方法和接口。 ECMAScript、DOM 和 BOM 尽管 ECMAScript 是一个重要的标准,但它并不是 JavaScript 唯一…

流式计算storm核心组件介绍以及入门案例---跟着就能在本地跑起来的storm项目

关于storm的基础,参照我这篇文章:流式计算storm 关于并发和并行,参照我这篇文章:并发和并行 关于storm的并行度解释,参照我这篇文章:storm的并行度解释 关于storm的流分组策略,参照我这篇文章:storm的流分组策略 关于storm的消息可靠机制,参照我这篇文章:storm的消息可靠机制 …

nginx使用gzip压缩文件---lz77算法---Haffman编码

为了提高页面的响应速度,可以从设置 nginx 的 gzip 和缓存这2方面入手,而为ttf,js,css等文件开启 gzip 和缓存能大大减少带宽的消耗. HTTP 的内容编码机制 Accept-Encoding 和 Content-Encoding 是 HTTP 中用来对[采用何种编码格式传输正文]进行协定的一对头部字段. 它的工作…

Javascript模块化编程

随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂。 网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。…

zookeeper基础整理

zookeeper简述 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件 ZooKeeper 使用 Java 所编写,但是支持 Java 和 C 两种编程语言。 提供的功能包括&#xf…

JS模块化编程require.js简介

一、为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码&…

CSS position属性

目前几乎所有主流的浏览器都支持position属性("inherit"除外,"inherit"不支持所有包括IE8和之前版本IE浏览器,IE9、IE10还没测试过),以下是w3school对position五个值的解释: 其中absol…

storm的并行度的解释--- ( 看完就能理解 )

关于storm的基础,参照我这篇文章:流式计算storm 关于并发和并行,参照我这篇文章:并发和并行 关于storm的并行度解释,参照我这篇文章:storm的并行度解释 关于storm的流分组策略,参照我这篇文章:storm的流分组策略 关于storm的消息可靠机制,参照我这篇文章:storm的消息可靠机…

JS--Console.log()详解

对于JavaScript程序的调试,相比于alert(),使用console.log()是一种更好的方式,原因在于:alert()函数会阻断JavaScript程序的执行,从而造成副作用;而console.log()仅在控制台中打印相关信息,因此…

订单单量监控v2

前段时间做了一个订单单量监控的项目,已经投入使用了,现在总结一下 前期的想法参考这篇文章 整体使用了storm实时计算框架和redis数据库,还有kafka消息队列 先上效果图,我们可以后期将数据展示出来,明显发现某天00点有单量突变的情况,明显是促销活动导致单量增加了 而后面的报…

iOS中的MVC设计模式

一、MVC概述模型-视图-控制器(MVC)是Xerox PARC在二十世纪八十年代为编程语言Smalltalk-80发明的一种软件设计模式,已被广泛使用。后来被推荐为Oracle旗下Sun公司Java EE平台的设计模式,并且受到…

iOS-MVVM-模式介绍

一、MVVM概述 MVVM 到底是什么?我们首先看一下MVC架构:我们看到的是一个典型的 MVC 设置。Model 呈现数据,View 呈现用户界面,而 View Controller 调节它两者之间的交互。Cool!稍微考虑一下,虽然 View 和 …

[数据库]---mysql数据库 使用binlog+canal或binlake进行数据库的复制

前言 在进行冷热分离的时候,需要将数据实时的复制在历史数据库中,我们使用的是binlogcanal的思想,将每次数据库数据的变更转换成消息发出来,然后再操作这些消息达到数据复制的 在京东,实现同样功能的组件,叫binlake 接下来详细说下: 1.Binl…

MAC下配置ZSH

MAC下面的终端是神器。而且苹果非常贴心的为我们准备好了ZSH。 可惜ZSH不是很好用,需要配合一些插件和模板:oh-my-zsh将bash切换为zsh chsh -s /bin/zsh其实还可以用which来定位(特别是ubuntu的童鞋) chsh -s which zsh 直接用zsh…

MAC下使用OpenSSL生成私钥和公钥

MAC OS自带了OpenSSL,直接在命令行里使用OPENSSL就可以。打开命令行工具,然后输入 openssl打开openssl,接着只要三句命令就可以搞定。1、打开Terminal--cd 到指定文件夹,如桌面Mac:~/Desktop $ openssl2、第一句命令:生成私钥&…

idea插件开发(01)---最简单的helloworld版,不需要知道原理,先跟我做一个最简单的弹框插件

前言 用了那么多idea插件,也想自己做一个插件,下面就是入门版本 你不需要先知道所有的概念,先跟着我的步骤做一个小;例子,后面再说原理 相关概念看后面一篇 本次以windos系统为例 开始 1.你得安装一个环境,供idea插件的开发使用 下载地址: https://www.jetbrains.com/idea/…

苹果封装的对称加密和非对称加密API

一、信息摘要算法5&#xff1a;MD51.系统库位置&#xff1a;<CommonCrypto/CommonHMAC.h>。2.非加密算法&#xff0c;属于哈希散列&#xff0c;不可逆&#xff0c;用于检验数据完整性。二、安全散列(哈希)算法SHA&#xff1a; 1.包含的散列算法&#xff1a;SHA-1&#xf…

ECC椭圆曲线加密算法原理

比特币使用椭圆曲线算法生成公钥和私钥&#xff0c;选择的是secp256k1曲线。与RSA&#xff08;Ron Rivest&#xff0c;Adi Shamir&#xff0c;Len Adleman三位天才的名字&#xff09;一样&#xff0c;ECC&#xff08;Elliptic Curves Cryptography&#xff0c;椭圆曲线加密&…

AES加密算法原理

一、摘要 AES&#xff08;The Advanced Encryption Standard&#xff09;是美国国家标准与技术研究所用于加密电子数据的规范&#xff0c;在2002年5月26日建立。它被预期能成为人们公认的加密包括金融、电信和政府数字信息的方法。AES 是一个新的可以用于保护电子数据的加密算法…

Base64编码解码原理

一. Base64编码由来 为什么会有Base64编码呢&#xff1f;因为有些网络传送渠道并不支持所有的字节&#xff0c;例如传统的邮件只支持可见字符的传送&#xff0c;像ASCII码的控制字符就不能通过邮件传送。这样用途就受到了很大的限制&#xff0c;比如图片二进制流的每个字节不可…