在实际开发中常常涉及到项目的升级,而该升级不能简单的上线就完事了,须要验证该升级是否兼容老的上线,所以可能须要并行运行两个项目一段时间进行数据比对和校验,待没问题后再进行上线。这其实就须要进行流量复制,把流量复制到其余服务器上,一种方式是使用如tcpcopy引流;另外咱们还可使用nginx的HttpLuaModule模块中的ngx.location.capture_multi进行并发执行来模拟复制。
构造两个服务:html
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调用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中的协程,经过它能够以同步的方式写非阻塞的代码实现。
此处要考虑记录失败的状况,对失败的数据进行重放仍是放弃根据本身业务作处理。mysql
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的切到新版,其他的还停留在老版。nginx
map $cookie___jda $ab_key { default "0"; ~^\d+\.\d+(?P<k>(1|3|5))\. "1"; }
使用map映射规则,即若是是到新版则等于"1",到老版等于“0”; 而后咱们就能够经过ngx.var.ab_key获取到该数据。git
location /abtest1 { if ($ab_key = "1") { echo_location /test1 ngx.var.args; } if ($ab_key = "0") { echo_location /test2 ngx.var.args; } }
此处也可使用proxy_pass到不一样版本的服务器上github
location /abtest2 { if ($ab_key = "1") { rewrite ^ /test1 break; proxy_pass http://backend1; } rewrite ^ /test2 break; proxy_pass http://backend2; }
首先下载lua-resty-cookieredis
cd /usr/openResty/lualib/resty/ wget https://raw.githubusercontent.com/cloudflare/lua-resty-cookie/master/lib/resty/cookie.lua
openResty.conf配置文件sql
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模型。api
location /api1 { echo_sleep 3; echo api1 : $arg_a; } location /api2 { echo_sleep 3; echo api2 : $arg_a; }
咱们使用echo_sleep等待3秒。
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://127.0.0.1/serial?a=22
api1 : 22 api2 : 22 6.0040001869202
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://127.0.0.1/concurrency1?a=22
api1 : 22 api2 : 22 3.0020000934601
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://127.0.0.1/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