设为首页 收藏本站
查看: 613|回复: 0

[经验分享] Nginx流量复制/AB测试/协程

[复制链接]

尚未签到

发表于 2016-12-27 09:30:40 | 显示全部楼层 |阅读模式
在实际开发中经常涉及到项目的升级,而该升级不能简单的上线就完事了,需要验证该升级是否兼容老的上线,因此可能需要并行运行两个项目一段时间进行数据比对和校验,待没问题后再进行上线。这其实就需要进行流量复制,把流量复制到其他服务器上,一种方式是使用如tcpcopy引流;另外我们还可以使用nginx的HttpLuaModule模块中的ngx.location.capture_multi进行并发执行来模拟复制。
 
构造两个服务
Java代码   DSC0000.png


  • location /test1 {  
  •     keepalive_timeout 60s;   
  •     keepalive_requests 1000;  
  •     content_by_lua '  
  •         ngx.print("test1 : ", ngx.req.get_uri_args()["a"])  
  •         ngx.log(ngx.ERR, "request test1")  
  •     ';  
  • }  
  • location /test2 {  
  •     keepalive_timeout 60s;   
  •     keepalive_requests 1000;  
  •     content_by_lua '  
  •         ngx.print("test2 : ", ngx.req.get_uri_args()["a"])  
  •         ngx.log(ngx.ERR, "request test2")  
  •     ';  
  • }  

  
通过ngx.location.capture_multi调用
Java代码  


  • location /test {  
  •      lua_socket_connect_timeout 3s;  
  •      lua_socket_send_timeout 3s;  
  •      lua_socket_read_timeout 3s;  
  •      lua_socket_pool_size 100;  
  •      lua_socket_keepalive_timeout 60s;  
  •      lua_socket_buffer_size 8k;  
  •   
  •      content_by_lua '  
  •          local res1, res2 = ngx.location.capture_multi{  
  •                { "/test1", { args = ngx.req.get_uri_args() } },  
  •                { "/test2", { args = ngx.req.get_uri_args()} },  
  •          }  
  •          if res1.status == ngx.HTTP_OK then  
  •              ngx.print(res1.body)  
  •          end  
  •          if res2.status ~= ngx.HTTP_OK then  
  •             --记录错误  
  •          end  
  •      ';  
  • }  

此处可以根据需求设置相应的超时时间和长连接连接池等;ngx.location.capture底层通过cosocket实现,而其支持Lua中的协程,通过它可以以同步的方式写非阻塞的代码实现。
 
此处要考虑记录失败的情况,对失败的数据进行重放还是放弃根据自己业务做处理。
 
AB测试
AB测试即多版本测试,有时候我们开发了新版本需要灰度测试,即让一部分人看到新版,一部分人看到老版,然后通过访问数据决定是否切换到新版。比如可以通过根据区域、用户等信息进行切版本。
 
比如京东商城有一个cookie叫做__jda,该cookie是在用户访问网站时种下的,因此我们可以拿到这个cookie,根据这个cookie进行版本选择。
 
比如两次清空cookie访问发现第二个数字串是变化的,即我们可以根据第二个数字串进行判断。
__jda=122270672.1059377902.1425691107.1425691107.1425699059.1
__jda=122270672.556927616.1425699216.1425699216.1425699216.1。
 
判断规则可以比较多的选择,比如通过尾号;要切30%的流量到新版,可以通过选择尾号为1,3,5的切到新版,其余的还停留在老版。
 
1、使用map选择版本 
Java代码  


  • map $cookie___jda $ab_key {  
  •     default                                       "0";  
  •     ~^\d+\.\d+(?P<k>(1|3|5))\.                    "1";  
  • }  

使用map映射规则,即如果是到新版则等于"1",到老版等于“0”; 然后我们就可以通过ngx.var.ab_key获取到该数据。
Java代码  


  • location /abtest1 {  
  •     if ($ab_key = "1") {  
  •         echo_location /test1 ngx.var.args;  
  •     }  
  •     if ($ab_key = "0") {  
  •         echo_location /test2 ngx.var.args;  
  •     }  
  • }  

此处也可以使用proxy_pass到不同版本的服务器上 
Java代码  


  • location /abtest2 {  
  •     if ($ab_key = "1") {  
  •         rewrite ^ /test1 break;  
  •         proxy_pass http://backend1;  
  •     }  
  •     rewrite ^ /test2 break;  
  •     proxy_pass http://backend2;  
  • }  

 
2、直接在Lua中使用lua-resty-cookie获取该Cookie进行解析
首先下载lua-resty-cookie
Java代码  


  • cd /usr/example/lualib/resty/  
  • wget https://raw.githubusercontent.com/cloudflare/lua-resty-cookie/master/lib/resty/cookie.lua  

 
Java代码  


  • location /abtest3 {  
  •     content_by_lua '  
  •   
  •          local ck = require("resty.cookie")  
  •          local cookie = ck:new()  
  •          local ab_key = "0"  
  •          local jda = cookie:get("__jda")  
  •          if jda then  
  •              local v = ngx.re.match(jda, [[^\d+\.\d+(1|3|5)\.]])  
  •              if v then  
  •                 ab_key = "1"  
  •              end  
  •          end  
  •   
  •          if ab_key == "1" then  
  •              ngx.exec("/test1", ngx.var.args)  
  •          else  
  •              ngx.print(ngx.location.capture("/test2", {args = ngx.req.get_uri_args()}).body)  
  •          end  
  •     ';  
  •   
  • }  

 首先使用lua-resty-cookie获 取cookie,然后使用ngx.re.match进行规则的匹配,最后使用ngx.exec或者ngx.location.capture进行处理。此 处同时使用ngx.exec和ngx.location.capture目的是为了演示,此外没有对ngx.location.capture进行异常处 理。
 
协程
Lua中没有线程和异步编程编程的概念,对于并发执行提供了协程的概念,个人认为协程是在A运行中发现自己忙则把CPU使用权让出来给B使用,最后A能从中断位置继续执行,本地还是单线程,CPU独占的;因此如果写网络程序需要配合非阻塞I/O来实现。
 
ngx_lua 模块对协程做了封装,我们可以直接调用ngx.thread API使用,虽然称其为“轻量级线程”,但其本质还是Lua协程。该API必须配合该ngx_lua模块提供的非阻塞I/O API一起使用,比如我们之前使用的ngx.location.capture_multi和lua-resty-redis、lua-resty- mysql等基于cosocket实现的都是支持的。
 
通过Lua协程我们可以并发的调用多个接口,然后谁先执行成功谁先返回,类似于BigPipe模型。
 
1、依赖的API 
Java代码  


  • location /api1 {  
  •     echo_sleep 3;  
  •     echo api1 : $arg_a;  
  • }  
  • location /api2 {  
  •     echo_sleep 3;  
  •     echo api2 : $arg_a;  
  • }  

 我们使用echo_sleep等待3秒。
 
2、串行实现
Java代码  


  • location /serial {  
  •     content_by_lua '  
  •         local t1 = ngx.now()  
  •         local res1 = ngx.location.capture("/api1", {args = ngx.req.get_uri_args()})  
  •         local res2 = ngx.location.capture("/api2", {args = ngx.req.get_uri_args()})  
  •         local t2 = ngx.now()  
  •         ngx.print(res1.body, "<br/>", res2.body, "<br/>", tostring(t2-t1))  
  •     ';  
  • }  

即一个个的调用,总的执行时间在6秒以上,比如访问http://192.168.1.2/serial?a=22
Java代码  


  • api1 : 22   
  • api2 : 22   
  • 6.0040001869202  

 
3、ngx.location.capture_multi实现
Java代码  


  • location /concurrency1 {  
  •     content_by_lua '  
  •         local t1 = ngx.now()  
  •         local res1,res2 = ngx.location.capture_multi({  
  •               {"/api1", {args = ngx.req.get_uri_args()}},  
  •               {"/api2", {args = ngx.req.get_uri_args()}}  
  •   
  •         })  
  •         local t2 = ngx.now()  
  •         ngx.print(res1.body, "<br/>", res2.body, "<br/>", tostring(t2-t1))  
  •     ';  
  • }  

直接使用ngx.location.capture_multi来实现,比如访问http://192.168.1.2/concurrency1?a=22
Java代码  


  • api1 : 22   
  • api2 : 22   
  • 3.0020000934601  

    
4、协程API实现 
Java代码  


  • location /concurrency2 {  
  •     content_by_lua '  
  •         local t1 = ngx.now()  
  •         local function capture(uri, args)  
  •            return ngx.location.capture(uri, args)  
  •         end  
  •         local thread1 = ngx.thread.spawn(capture, "/api1", {args = ngx.req.get_uri_args()})  
  •         local thread2 = ngx.thread.spawn(capture, "/api2", {args = ngx.req.get_uri_args()})  
  •         local ok1, res1 = ngx.thread.wait(thread1)  
  •         local ok2, res2 = ngx.thread.wait(thread2)  
  •         local t2 = ngx.now()  
  •         ngx.print(res1.body, "<br/>", res2.body, "<br/>", tostring(t2-t1))  
  •     ';  
  • }  

使用ngx.thread.spawn创建一个轻量级线程,然后使用ngx.thread.wait等待该线程的执行成功。比如访问http://192.168.1.2/concurrency2?a=22
Java代码  


  • api1 : 22   
  • api2 : 22   
  • 3.0030000209808  

   
其有点类似于Java中的线程池执行模型,但不同于线程池,其每次只执行一个函数,遇到IO等待则让出CPU让下一个执行。我们可以通过下面的方式实现任意一个成功即返回,之前的是等待所有执行成功才返回。
Java代码  


  • local  ok, res = ngx.thread.wait(thread1, thread2) 

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-320023-1-1.html 上篇帖子: 实现基于nginx的tomcat负载均衡和集群配置 下篇帖子: nginx解决www和wap的选择访问
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表