高并发 Nginx+Lua OpenResty系列(11)——流量复制/AB测试/协程
流量复制
在实际开发中经常涉及到项目的升级,而该升级不能简单的上线就完事了,需要验证该升级是否兼容老的上线,因此可能需要并行运行两个项目一段时间进行数据比对和校验,待没问题后再进行上线。这其实就需要进行流量复制,把流量复制到其他服务器上,一种方式是使用如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 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<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/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
';
}
首先使用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://127.0.0.1/serial?a=22
api1 : 22
api2 : 22
6.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, "<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
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://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
高并发 Nginx+Lua OpenResty系列(11)——流量复制/AB测试/协程的更多相关文章
- 高并发 Nginx+Lua OpenResty系列(3)——模块指令
Nginx Lua 模块指令 Nginx共11个处理阶段,而相应的处理阶段是可以做插入式处理,即可插拔式架构:另外指令可以在http.server.server if.location.locatio ...
- 高并发 Nginx+Lua OpenResty系列(10)——商品详情页
本章以京东商品详情页为例,京东商品详情页虽然仅是单个页面,但是其数据聚合源是非常多的,除了一些实时性要求比较高的如价格.库存.服务支持等通过AJAX异步加载加载之外,其他的数据都是在后端做数据聚合然后 ...
- 高并发 Nginx+Lua OpenResty系列(9)——HTTP服务
此处我说的HTTP服务主要指如访问京东网站时我们看到的热门搜索.用户登录.实时价格.实时库存.服务支持.广告语等这种非Web页面,而是在Web页面中异步加载的相关数据.这些服务有个特点即访问量巨大.逻 ...
- 高并发 Nginx+Lua OpenResty系列(2)——Nginx Lua API
Nginx Lua API 和一般的Web Server类似,我们需要接收请求.处理并输出响应.而对于请求我们需要获取如请求参数.请求头.Body体等信息:而对于处理就是调用相应的Lua代码即可:输出 ...
- 高并发 Nginx+Lua OpenResty系列(1)——环境搭建
OpenResty是一款基于Nginx的高性能负载均衡服务器容器,简单来说是Nginx+Lua.结合了Lua语言来对Nginx进行扩展,使得在Nginx上具有web容器功能. OpenResty运行环 ...
- 高并发 Nginx+Lua OpenResty系列(8)——Lua模版渲染
模版渲染 动态web网页开发是Web开发中一个常见的场景,比如像京东商品详情页,其页面逻辑是非常复杂的,需要使用模板技术来实现.而Lua中也有许多模板引擎,如目前京东在使用的lua-resty-tem ...
- 高并发 Nginx+Lua OpenResty系列(5)——Lua开发库Redis
Redis客户端 lua-resty-redis是为基于cosocket API的ngx_lua提供的Lua redis客户端,通过它可以完成Redis的操作.默认安装OpenResty时已经自带了该 ...
- 高并发 Nginx+Lua OpenResty系列(4)——Lua 模块开发
在实际开发中,不可能把所有代码写到一个大而全的lua文件中,需要进行分模块开发:而且模块化是高性能Lua应用的关键.使用require第一次导入模块后,所有Nginx 进程全局共享模块的数据和代码,每 ...
- 高并发 Nginx+Lua OpenResty系列(7)——Lua开发库json
JSON库 在进行数据传输时JSON格式目前应用广泛,因此从Lua对象与JSON字符串之间相互转换是一个非常常见的功能:目前Lua也有几个JSON库,如:cjson.dkjson.其中cjson的语法 ...
随机推荐
- flutter学习之环境配置
1.Android SDK通常目录: 用户->用户名->AppData->Local=>Android->Sdk 2.不知道的情况下,打开Android Studio,然 ...
- Leetcode题解 - 贪心算法部分简单题目代码+思路(860、944、1005、1029、1046、1217、1221)
leetcode真的是一个学习阅读理解的好地方 860. 柠檬水找零 """ 因为用户支付的只会有5.10.20 对于10元的用户必须找一个5 对于20元的用户可以找(三 ...
- Python面向对象-概念、类、实例
OOP——Object Oriented Programming,面向对象编程,是一种程序设计思想.该思想将对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 区别于面向过程的程序设计即把计 ...
- Dynamics 365中的Client API form context (formContext)
适用于Dynamics 365 for Customer Engagement apps 9.x版本. 本文是一篇翻译,原文来源是微软官方文档. 本文链接:https://www.cnblogs.co ...
- LL(1)文法的判断,递归下降分析程序
1. 文法 G(S): (1)S -> AB (2)A ->Da | ε (3)B -> cC (4)C -> aADC | ε (5)D -> b | ε 验证文法 G ...
- 【30天自制操作系统】day04:C语言与目前执行流程图
用 C 语言直接写入内存 原来依靠汇编 void io_hlt(void); void write_mem8(int addr, int data); void HariMain(void){ int ...
- HTTP 400 与 SpringMVC的 HttpPutFormContentFilter、FormContentFilter
前端发送了一个http PUT请求,如下, json_xml: {,},,}},,},,}},},}},,},{,}],,},,}},,,,},,}},},}},,},{,}],,},,}},,},{ ...
- Hive 性能测试工具 hive-testbench
下载: yum -y install gcc gcc-c++ maven 下载地址Github:https://github.com/hortonworks/hive-testbench/git cl ...
- C#通用查询器
很多通用查询器,对查询条件中的AND及OR的支持度不是很好,要么全部是AND要么全部是OR.笔者通过一段时间的摸索,终于完成了一个自己较为满意的通用查询器, 可以实现多条件的AND及OR,现将实现过程 ...
- Hadoop生态体系组件
目录: 一.本地数据集上传到数据仓库Hive 二.Hive的基本操作 三.Hive.Mysql.HBase数据互导 正文: 一.本地数据集上传到数据仓库Hive 1.实验数据集的下载 2.数据集的预处 ...