基于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的backend設置diversion02.lua???>舊項目02的backend設置
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 enddiversion01.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最終負載指向的位置,從而實現實時的控制流量方向
總結
以上是生活随笔為你收集整理的基于abtest思想的流量切换(nginx lua redis)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 格式化分区,Androi
- 下一篇: 强制消除Xcode警告的方法