博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
高并发 Nginx+Lua OpenResty系列(11)——流量复制/AB测试/协程
阅读量:4627 次
发布时间:2019-06-09

本文共 5545 字,大约阅读时间需要 18 分钟。

流量复制

在实际开发中经常涉及到项目的升级,而该升级不能简单的上线就完事了,需要验证该升级是否兼容老的上线,因此可能需要并行运行两个项目一段时间进行数据比对和校验,待没问题后再进行上线。这其实就需要进行流量复制,把流量复制到其他服务器上,一种方式是使用如引流;另外我们还可以使用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 test1")    ';}

通过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
(1|3|5))\. "1";}

使用映射规则,即如果是到新版则等于"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/openResty/lualib/resty/wget https://raw.githubusercontent.com/cloudflare/lua-resty-cookie/master/lib/resty/cookie.lua

openResty.conf配置文件

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    ';}

首先使用获取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, "
", res2.body, "
", tostring(t2-t1)) ';}

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

api1 : 22api2 : 226.0040001869202

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, "
", res2.body, "
", tostring(t2-t1)) ';}

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

api1 : 22api2 : 223.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, "
", res2.body, "
", tostring(t2-t1)) ';}

使用创建一个轻量级线程,然后使用等待该线程的执行成功。比如访问http://127.0.0.1/concurrency2?a=22

api1 : 22api2 : 223.0030000209808

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

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

Lua协程参考资料

转载于:https://www.cnblogs.com/babycomeon/p/11109517.html

你可能感兴趣的文章
洛谷 P5019 铺设道路(差分)
查看>>
CSS层叠样式
查看>>
Pycharm初始创建项目和环境搭建(解决aconda库文件引入不全等问题)
查看>>
Spring Boot启动过程(二)
查看>>
使用Python命令创建jenkins的job
查看>>
第十二周编程总结
查看>>
暑假集训D15总结
查看>>
对类型“DevExpress.Xpf.Grid.GridControl”的构造函数执行符合指定的绑定约束的调用时引发了异常。...
查看>>
dogse入门指南
查看>>
Spring 整合quartz 时 定时任务被调用两次以及quartz 的配置
查看>>
oracle测试环境表空间清理
查看>>
余额宝技术架构读后感
查看>>
1.lamp网站构建
查看>>
狼人杀
查看>>
《lua程序设计(第二版)》学习笔记(五)-- 函数基础
查看>>
【CF EDU59 E】 Vasya and Binary String (DP)
查看>>
Catel(翻译)-为什么选择Catel
查看>>
angular轮播图
查看>>
指针小白:修改*p与p会对相应的地址的变量产生什么影响?各个变量指针的长度为多少?...
查看>>
文本相关CSS
查看>>