在实际开发中经常涉及到项目的升级,而该升级不能简单的上线就完事了,需要验证该升级是否兼容老的上线,因此可能需要并行运行两个项目一段时间进行数据比对和校验,待没问题后再进行上线。这其实就需要进行流量复制,把流量复制到其他服务器上,一种方式是使用如tcpcopy引流;另外我们还可以使用nginx的HttpLuaModule模块中的ngx.location.capture_multi进行并发执行来模拟复制。
构造两个服务
1. location /test1 {
2. keepalive_timeout 60s;
3. 1000;
4. content_by_lua '
5. "test1 : ", ngx.req.get_uri_args()["a"])
6. "request test1")
7. ';
8. }
9. location /test2 {
10. keepalive_timeout 60s;
11. 1000;
12. content_by_lua '
13. "test2 : ", ngx.req.get_uri_args()["a"])
14. "request test2")
15. ';
16. }
通过ngx.location.capture_multi调用
1. location /test {
2. lua_socket_connect_timeout 3s;
3. lua_socket_send_timeout 3s;
4. lua_socket_read_timeout 3s;
5. 100;
6. lua_socket_keepalive_timeout 60s;
7. lua_socket_buffer_size 8k;
8.
9. content_by_lua '
10. local res1, res2 = ngx.location.capture_multi{
11. "/test1", { args = ngx.req.get_uri_args() } },
12. "/test2", { args = ngx.req.get_uri_args()} },
13. }
14. if
15. ngx.print(res1.body)
16. end
17. if
18. --记录错误
19. end
20. ';
21. }
此处可以根据需求设置相应的超时时间和长连接连接池等;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选择版本
1. map $cookie___jda $ab_key {
2. default "0";
3. 1|3|5))\. "1";
4. }
使用map映射规则,即如果是到新版则等于"1",到老版等于“0”; 然后我们就可以通过ngx.var.ab_key获取到该数据。
1. location /abtest1 {
2. if ($ab_key = "1") {
3. echo_location /test1 ngx.var.args;
4. }
5. if ($ab_key = "0") {
6. echo_location /test2 ngx.var.args;
7. }
8. }
此处也可以使用proxy_pass到不同版本的服务器上
1. location /abtest2 {
2. if ($ab_key = "1") {
3. break;
4. //backend1;
5. }
6. break;
7. //backend2;
8. }
2、直接在Lua中使用lua-resty-cookie获取该Cookie进行解析
首先下载lua-resty-cookie
1. cd /usr/example/lualib/resty/
2. wget https://raw.githubusercontent.com/cloudflare/lua-resty-cookie/master/lib/resty/cookie.lua
1. location /abtest3 {
2. content_by_lua '
3.
4. "resty.cookie")
5. new()
6. "0"
7. "__jda")
8. if
9. 1|3|5)\.]])
10. if
11. "1"
12. end
13. end
14.
15. if ab_key == "1"
16. "/test1", ngx.var.args)
17. else
18. "/test2", {args = ngx.req.get_uri_args()}).body)
19. end
20. ';
21.
22. }
首先使用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
1. location /api1 {
2. 3;
3. echo api1 : $arg_a;
4. }
5. location /api2 {
6. 3;
7. echo api2 : $arg_a;
8. }
我们使用echo_sleep等待3秒。
2、串行实现
1. location /serial {
2. content_by_lua '
3. local t1 = ngx.now()
4. "/api1", {args = ngx.req.get_uri_args()})
5. "/api2", {args = ngx.req.get_uri_args()})
6. local t2 = ngx.now()
7. "<br/>", res2.body, "<br/>", tostring(t2-t1))
8. ';
9. }
即一个个的调用,总的执行时间在6秒以上,比如访问http://192.168.1.2/serial?a=22
1. api1 : 22
2. api2 : 22
3. 6.0040001869202
3、ngx.location.capture_multi实现
1. location /concurrency1 {
2. content_by_lua '
3. local t1 = ngx.now()
4. local res1,res2 = ngx.location.capture_multi({
5. "/api1", {args = ngx.req.get_uri_args()}},
6. "/api2", {args = ngx.req.get_uri_args()}}
7.
8. })
9. local t2 = ngx.now()
10. "<br/>", res2.body, "<br/>", tostring(t2-t1))
11. ';
12. }
直接使用ngx.location.capture_multi来实现,比如访问http://192.168.1.2/concurrency1?a=22
1. api1 : 22
2. api2 : 22
3. 3.0020000934601
4、协程API实现
1. location /concurrency2 {
2. content_by_lua '
3. local t1 = ngx.now()
4. local function capture(uri, args)
5. return
6. end
7. "/api1", {args = ngx.req.get_uri_args()})
8. "/api2", {args = ngx.req.get_uri_args()})
9. local ok1, res1 = ngx.thread.wait(thread1)
10. local ok2, res2 = ngx.thread.wait(thread2)
11. local t2 = ngx.now()
12. "<br/>", res2.body, "<br/>", tostring(t2-t1))
13. ';
14. }
使用ngx.thread.spawn创建一个轻量级线程,然后使用ngx.thread.wait等待该线程的执行成功。比如访问http://192.168.1.2/concurrency2?a=22
1. api1 : 22
2. api2 : 22
3. 3.0030000209808
其有点类似于Java中的线程池执行模型,但不同于线程池,其每次只执行一个函数,遇到IO等待则让出CPU让下一个执行。我们可以通过下面的方式实现任意一个成功即返回,之前的是等待所有执行成功才返回。
1. local ok, res = ngx.thread.wait(thread1, thread2)
标签:AB,协程,lua,local,args,Nginx,cookie,location,ngx
From: https://blog.51cto.com/u_6186189/6458539