OpenResty 通过lua脚本扩展nginx功能,可提供负载均衡、请求路由、安全认证、服务鉴权、流量控制与日志监控等服务。很多大厂比如京东、360都在生产环境是用了这个应用程序。2015年底老罗营销锤子的时候顺便让这个程序在程序员圈中火了一把。
OpenResty 处理请求大致分为四个阶段,可在不同阶段使用lua脚本来定制不同行为:
初始化阶段(Initialization Phase)可以执行的nginx_lua命令:init_by_lua,init_worker_by_lua
重写与访问阶段(Rewrite / Access Phase)可以执行的nginx_lua命令:ssl_certificate_by_lua,set_by_lua,rewrite_by_lua,access_by_lua
内容生成阶段(Content Phase)可以执行的nginx_lua命令:content_by_lua,balancer_by_lua,header_filter_by_lua,body_filter_by_lua
日志记录阶段(Log Phase)可以执行的nginx_lua命令:log_by_lua
下面通过两篇文章中的2个常用场景来说明下,代码参见githubhttps://github.com/crackwl8/OpenRestyDemo1。对于小型web应用来说,负载均衡、请求路由、流量控制最常用。请求路由跟各个应用的设计相关,这里就不描述了,大家参考nginx说明。下面主要描述下负载均衡和流量控制配置。
中小型web应用一般架构如下,使用多台机器部署OpenResty+Keeplived来作为web服务器入口,应用可能分为多层多模块,还有redis等缓存这里就没有详细画出来了。
1.负载均衡配置
(1)Nginx自带的负载均衡配置
负载均衡可以使用Nginx的配置(参见代码nginx.conf),如
#设定负载均衡的服务器列表
upstream mysvr1 {
#weigth参数表示权值
server 192.168.8.1:8080 max_fails=2 fail_timeout=5s weight=2;
server 192.168.8.2:8081 max_fails=2 fail_timeout=5s weight=1;
server 192.168.8.3:8082 max_fails=2 fail_timeout=5s weight=4;
}
upstream mysvr2 {
server 192.168.9.1:8080 down;
server 192.168.9.2:8080 weight=2;
server 192.168.9.3:8080;
server 192.168.9.4:8080 backup;
}
此处举例都是按照静态权重轮询WRR进行负载均衡;Nginx还支持轮询、iphash等负载均衡算法,还有一些动态调度算法需要下载相应的模块来支持。Nginx如何来检测后端服务器的健康状态呢? 方法一使用max_fails配置,nginx在fail_timeout设定的时间内与后端服务器通信失败的次数超过max_fails设定的次数,则认为这个服务器不在起作用;在接下来的 fail_timeout时间内,nginx不再将请求分发给失效的server 。方法二安装使用第三方模块nginx_upstream_check_module。在upstream下新增配置 check interval=3000 rise=2 fall=5 timeout=1000 type=http;来进行定时检测。
(2)OpenResty负载均衡配置
负载均衡使用OpenResty配置就更方便灵活了,可以用lua脚本来实现自己的负载均衡策略,例如
upstream mysvr3 {
server 192.168.10.1:8080;
server 192.168.10.2:8080;
balancer_by_lua_file lua/balancer.lua;
}
在balancer.lua脚本里面来实现负载均衡策略,本文例子比较简单,大家可以根据自己需要进行重写。那么OpenResty如何来检测后端服务器的健康状态呢?答案是使用lua-resty-upstream-healthcheck模块。每个Nginx的worker启动检测,将检测结果保存到lua_shared_dict共享内存,其他模块的lua代码从upstream模块的接口upstream.get_primary_servers()中获取peer.down的值获得状态信息进行控制。
这里还提一点,如果应用服务器需要不断扩容,那么就会有很多server需要增加到配置文件中,需要引入注册中心,然后可以使用lua脚本编码来动态扩容或者第三方模块Ngx_http_dyups_module。
2.流量控制
(1)Nginx自带的流量控制配置
流量控制配置分为限制请求和限制连接。
限制请求配置例子
http {
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server{
location /test/limit {
limit_req zone=mylimit burst=100 nodelay;
}
}
}
相同ip限制每秒10个请求,还可以通过 limit_conn_zone限制客户端连接数和速率。限制连接例子
http {
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn_log_level error; # 被限流后的日志级别
limit_conn_status 503; # 被限流后返回的状态码
...
server {
...
location /limit {
limit_conn addr 100; # Nginx最多同时并发处理100个连接
}
...
}
这里需要注意在Nginx流量上游还有代理或负载均衡服务器情况下,$binary_remote_addr不能获得用户的真正IP,而是代理服务器IP。这种情况下需要使用 realip_module 模块来获取。
(2)OpenResty流量控制配置
Openresty提供了lua-resty-limit-traffic模块进行限流,模块实现了limit.conn和limit.req的功能和算法,分别对应限制连接和限制请求,跟nginx自带的限流算法一致。使用access_by_lua*命令可以实现lua脚本限流。
限制连接limit.conn
nginx配置如下:
location /test/limit2 {
access_by_lua_file lua/limit_conn.lua;
default_type text/plain;
content_by_lua_block {
ngx.print('goodluck')
ngx.sleep(0.01)
}
log_by_lua_file src/log.lua;
}
limit_conn.lua代码如下:
local limit_conn = require "resty.limit.conn"
-- 对于内部重定向或子请求,不进行限制。因为这些并不是真正对外的请求。
if ngx.req.is_internal() then
return
end
-- 限制一个 ip 客户端最大 5 个并发请求
-- burst 设置为 0,如果超过最大的并发请求数,则直接返回503,
-- 如果此处要允许突增的并发数,可以修改 burst 的值(漏桶的桶容量)
-- 最后一个参数其实是你要预估这些并发(或者说单个请求)要处理多久,以便于对桶里面的请求应用漏桶算法
local lim, err = limit_conn.new("my_limit_conn_store", 5, 5, 0.1)
if not lim then
ngx.log(ngx.ERR, "failed to instantiate a resty.limit.conn object: ", err)
return ngx.exit(500)
end
local key = ngx.var.binary_remote_addr
-- commit 为true 代表要更新shared dict中key的值,
-- false 代表只是查看当前请求要处理的延时情况和前面还未被处理的请求数
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end
-- 如果请求连接计数等信息被加到shared dict中,则在ctx中记录下,
-- 因为后面要告知连接断开,以处理其他连接
if lim:is_committed() then
local ctx = ngx.ctx
ctx.limit_conn = lim
ctx.limit_conn_key = key
ctx.limit_conn_delay = delay
end
local conn = err
-- 其实这里的 delay 肯定是上面说的并发处理时间的整数倍,
-- 举个例子,每秒处理100并发,桶容量200个,当时同时来500个并发,则200个拒掉
-- 100个在被处理,然后200个进入桶中暂存,被暂存的这200个连接中,0-100个连接其实应该延后0.5秒处理,
-- 101-200个则应该延后0.5*2=1秒处理(0.5是上面预估的并发处理时间)
if delay >= 0.001 then
ngx.sleep(delay)
end
这个地方需要注意,要在连接处理完后进行后置处理,参见log.lua
local ctx = ngx.ctx
local lim = ctx.limit_conn
if lim then
local key = ctx.limit_conn_key
local conn, err = lim:leaving(key, 0.1)
if not conn then
ngx.log(ngx.ERR,
"failed to record the connection leaving ",
"request: ", err)
return
end
end
限制连接limit.req
Nginx配置
location /test/limit3 {
access_by_lua_file lua/limit_req.lua;
default_type text/plain;
content_by_lua_block {
ngx.print('goodluck')
ngx.sleep(0.01)
}
}
limit_req.lua代码如下:
local limit_req = require "resty.limit.req"
-- 限制请求速率为10 req/sec,并且允许5 req/sec的突发请求
local lim, err = limit_req.new("my_limit_req_store", 10, 5)
if not lim then
ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
return ngx.exit(500)
end
-- 使用ip地址作为限流的key,在有代理或cdn情况下binary_remote_addr不是真实ip,需要处理。
-- 还可以使用host、useragent等标识作为key来实现更宽或更细粒度的限速
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end
if delay > 0 then
-- 第二个参数(err)保存着超过请求速率的请求数
local excess = err
ngx.sleep(delay) --非阻塞sleep(秒)
end