Fising's Blog

all about web application development

NGINX + LUA 过载保护方案研究

问题背景

由于近期电商促销活动频繁、力度大,商城流量明显升高,偶发因为负载过高导致访问延迟高、 甚至发生 50x 错误的情况。

解决思路

主要分三方面考虑:

  1. 商城程序调优,数据库查询优化。由于新版商城已经在开发中,所以老商城以稳定为主。并不会做过多的调整。
  2. 硬件扩展。为应对促销季节的大流量,临时提升服务器带宽;增加数据库服务器的 CPU 核心数和内存容量。
  3. 必要的时候做服务降级和过载保护

方案探索

lua-nginx-module 是一个 nginx 非官方模块,提供了 nginx 服务器中内嵌 lua 脚本的能力。通过在运行时将控制权交给 lua 解释器来实现弹性扩展。

lua-nginx-module 可以单独安装,也可以通过集成环境 openresty 来安装。这里推荐使用后者,因为该套件集成了众多组件,包括 nginx、lua module、redis module mysql module 等等,使用起来更加方便。

S1 安装 openresty

以我们生产环境应用最多的 CentOS 为例:

安装位置为 /usr/local/openresty. 看一下目录下的文件:

可以看到,openresty 已经集成了 nginx、lua、luajit、openssl 和 pcre 等组件。再进去 bin 目录看一下:

openresty 被软连接到 nginx。那么理论上我们也就可以通过 openresty 来执行对 nginx 的操作。

S2 配置 nginx

编辑 /usr/local/openresty/nginx/conf/nginx.conf 文件,在 http 块中添加两行:

第一行的作用是关闭 lua 代码缓存。这样我们每次修改 lua 脚本后就不需要重新 reload nginx,方便调试。但是这样会极大地影响性能,生产环境需要关闭。第二行代码的作用是开辟一个共享内存区域,用于存储 lua 共享变量,变量的名字是 global,空间大小为 30m。

在 server 块中,我们添加一个 location 配置:

保存,通过 openresty -t 测试配置文件。然后通过 openresty -s reload 来重载配置。

这时,通过浏览器打开网站,就可以正常访问了。如下图所示:

这里的 hello world 正是 lua 脚本通过 ngx.say API 响应的。

S3 制定策略

在请求到达后,我们通过 access_by_lua* 系列指令,将请求控制权拿到。对请求进行统计。如果某一时间段内的请求数超过我们设定的某个阈值,即拦截该请求,输出系统繁忙页面。从而将用户请求拦截在应用服务器之前。

我们通过上面开辟的内存共享区,对于放行的请求,将请求数 + 1,以此计数。放行前,如果过去一秒请求数超过限制,则对该请求进行拦截。

在 nginx.conf 的 server 块中,添加配置:

在请求进行到 access 阶段的时候,将控制权交给 access.lua 脚本。这样我们就可以在请求被真正处理之前,做一些统计工作。

S4 编写 lua 脚本

下面是 access.lua 脚本的内容:

S5 测试

开启 apache ab 对网站进行并发请求,同时刷新浏览器,请求网站。当并发请求数很小的时候,刷新浏览器,页面正常输出 hello world 内容。随着并发数的提高,刷新浏览器,页面出现系统繁忙的比例越来越高:

待解决问题

虽然测试效果实现了预期的目标,但是该方案应用到生产环境还有很多问题要解决。比如任意滑动窗口的访问数统计算法(队列?)、共享变量的线程安全、如何科学设置阈值等。

发表评论