Openresty流量复制/AB测试/协程_jinnianshilongnian的专栏-CSDN博客

标签: | 发表时间:2021-06-09 18:31 | 作者:
出处:https://blog.csdn.net

流量复制

在实际开发中经常涉及到项目的升级,而该升级不能简单的上线就完事了,需要验证该升级是否兼容老的上线,因此可能需要并行运行两个项目一段时间进行数据比对和校验,待没问题后再进行上线。这其实就需要进行流量复制,把流量复制到其他服务器上,一种方式是使用如 tcpcopy引流;另外我们还可以使用nginx的HttpLuaModule模块中的ngx.location.capture_multi进行并发执行来模拟复制。

 

构造两个服务

      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调用

      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选择版本 

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

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

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

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

      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

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

 

      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 

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

 我们使用echo_sleep等待3秒。

 

2、串行实现

      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

      api1 : 22 
api2 : 22 
6.0040001869202

 

3、ngx.location.capture_multi实现

      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

      api1 : 22 
api2 : 22 
3.0020000934601

    

4、协程API实现 

      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

      api1 : 22 
api2 : 22 
3.0030000209808

   

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

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

 

Lua协程参考资料

《Programming in Lua》

http://timyang.net/lua/lua-coroutine-vs-java-wait-notify/

https://github.com/andycai/luaprimer/blob/master/05.md

http://my.oschina.net/wangxuanyihaha/blog/186401

http://manual.luaer.cn/2.11.html

 

相关 [openresty 流量 复制] 推荐:

Openresty流量复制/AB测试/协程_jinnianshilongnian的专栏-CSDN博客

- -
在实际开发中经常涉及到项目的升级,而该升级不能简单的上线就完事了,需要验证该升级是否兼容老的上线,因此可能需要并行运行两个项目一段时间进行数据比对和校验,待没问题后再进行上线. 这其实就需要进行流量复制,把流量复制到其他服务器上,一种方式是使用如. tcpcopy引流;另外我们还可以使用nginx的HttpLuaModule模块中的ngx.location.capture_multi进行并发执行来模拟复制.

nginx 流量复制

- - 开源软件 - ITeye博客
# 后端的Web服务器可以通过X-Forwarded-For获取用户真实IP. #设置 $url ==原url. #新接口地址改变的,需要转换url. #新接口地址改变的,需要转换url. 已有 0 人发表留言,猛击->> 这里<<-参与讨论. —软件人才免语言低担保 赴美带薪读研.

[转]推荐OpenResty - Nginx全能插件版

- - 天空极速
官网: http://openresty.org/. 虽然是中国人做的,但没几个汉字……. 我用Nginx,是这样一个过程:. 1、系统rpm中的nginx,能让其跑起来. OpenResty,是淘宝一位大牛(agentzh)集成的包含N多好插件的Nginx捆绑源码包,这位仁兄自称Nginx最活跃的第三方模块开发人员哦.

在 OpenResty 里实现进程间通讯

- - SegmentFault 最新的文章
在 Nginx 里面,每个 worker 进程都是平等的. 但是有些时候,我们需要给它们分配不同的角色,这时候就需要实现进程间通讯的功能. 一种简单粗暴但却被普遍使用的方案,就是每个进程划分属于自己的 list 类型的 shdict key,每隔一段时间查看是否有新消息. 这种方式优点在于实现简单,缺点在于难以保证实时性.

Openresty+Lua+Redis灰度发布 - K‘e0llm - 博客园

- -
灰度发布,简单来说,就是根据各种条件,让一部分用户使用旧版本,另一部分用户使用新版本. 百度百科中解释:灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式. AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面 来.

流量生意

- flypen - 张磊的blog
前些日子网上盛传某联盟的按月分成数据,其中番茄花园、雨木林风的分成都高达百万. 有人惊呼:原来做盗版软件这么赚钱. 也有人质疑:他们怎么可能赚这么多钱. 他们确实很赚钱,简单说,这就是流量生意. 为什么几乎每个巨头都有一个“网站联盟”. 为什么Google当年愿意付费推广Firefox. 为什么,推广Firefox、自己做Chrome的同时,把微软当作对手的Google还特意提供“优化的Internet Explorer”.

mysql主从复制

- - SQL - 编程语言 - ITeye博客
从库的配置,mysql5.5不支持配置文件的配置了,问了数据库的人,用命令行指定. 修改从库的配置 #default-storage-engine = InnoDB #修改 default-storage-engine = blackhole server-id = 11215004 #新增 replicate-do-db = test log-bin = mysql-bin #新增 binlog_format = row.

linux 查看流量

- - 开源软件 - ITeye博客
在Linux下怎么看网络流量. 在Windows下,我们可以很方便的通过360来查看网络流量,知道哪个进程占用的网络带宽比较多. 那在Linux下怎么看流量呢,对于Web服务器来说这是很重要的. 下面这边博客很仔细的介绍了Linux下看流量的方法:. Linux 各种查看网卡流量的方法  http://jasonyong.blog.51cto.com/47753/174197.

MySQL的主从复制、半同步复制、主主复制详解

- - 学习笔记
复制其最终目的是让一台服务器的数据和另外的服务器的数据保持同步,已达到数据冗余或者服务的负载均衡. 一台主服务器可以连接多台从服务器,并且从服务器也可以反过来作为主服务器. 主从服务器可以位于不同的网络拓扑中,由于mysql的强大复制功能,其复制目标可以是所有的数据库,也可以是某些数据库,甚至是某个数据库中的某些表进行复制.

数据库复制-Goldengate

- - 人月神话的BLOG
参考: http://wenku.baidu.com/view/4fd7ea22bcd126fff7050b5d.html. GoldenGate TDM(交易数据管理)软件是一种基于日志的结构化数据复制软件,它通过解析源数据库在线日志或归档日志获得数据的增删改变化,再将这些变化应用到目标数据库,实现源数据库与目标数据库同步、双活.