nginx+lua打造10K qps+的web应用

背景篇

  由于项目流量越来越大,之前的nginx+php-fpm的架构已经难以承受峰值流量的冲击,春节期间集群负载一度长时间维持0%的idle,于是这段时间逐渐对旧系统进行重构。

  受高人指点,发现lua这个好东西。因此在技术选型上,我们使用lua代替部分的php逻辑,比如请求的过滤。lua是一种可以嵌入nginx配置文件的动态语言,结合nginx的请求处理过程(参见另一篇博文),lua可以在这些阶段接管请求的处理。

  我们的环境使用openresty搭建,openresty包括了很多nginx常用扩展,对于没有定制过nginx代码的我们来说比较方便。

  这里有一句比较关键的话,nginx配置文件的定义,是“声明”性质的,而不是“过程”性质的。nginx处理请求的阶段,是按一定顺序执行的,无论配置文件写的顺序如何都不影响它们的执行顺序,比如set一定在content之前。我们在项目中常能用到的:set_by_lua,可以用来进行变量的计算,access_by_lua,可以用来设置访问权限,content_by_lua是用来生成返回的内容,log_by_lua用来设置日志。

(lua的基本语法可以先参考这篇http://17173ops.com/tag/nginx_lua#toc12,个人觉得写的很清楚,很易懂。lua中需要用到的nginx的api参考http://wiki.nginx.org/HttpLuaModule)

使用lua编程要注意的问题:

1.lua不能对空数组(nil)进行索引!

2.lua的异常处理。比如的cjson库,在解析失败的时候,会直接抛异常从而中断脚本的执行,这里可以用cjson.safe来代替cjson,也可以采用这样的写法:

1 cache = switcher:get(key)
2 ret,errmsg = pcall(cjson.decode,cache);
3 if ret then
4     return errmsg;
5 else
6     return false;
7 end

就相当于在脚本中捕获异常,也可以封装try...catch

3.lua的字符串连接操作,也就是..,只支持字符串之间的连接,不支持字符串+数字或者是字符串+布尔,必须要显式转换类型

4.不要使用lua原生的io库,这会导致nginx进程阻塞!最好使用例如ngx.location.capture这样的函数,将io事件托管给nginx

实现篇

  我们的应用场景,是应对大量客户端(android,ios)的请求(4台linux服务器,应对10K+的qps),而业务逻辑相对简单,更多的是希望做流量的过滤。为了保护后端模块不会被突然上升的流量击垮,我们必须有一个强有力的前端,能较为轻松的抗住最大峰值流量,并进行相应的操作。这里我们用白名单的实现为例。贴上部分业务逻辑代码。因为某些原因,代码经过了删减,不能保证能运行,只是示例。

  1 local cjson = require "cjson";
  2 local agent = ngx.req.get_headers()["user-agent"];
  3 local switcher = ngx.shared.dict;
  4
  5 local UPLOAD_OK = ‘{"errno":0,"msg":""}‘;
  6 local UPLOAD_FAIL = ‘{"errno":-1,"msg":""}‘;
  7 local SHUT_DOWN = ‘{"errno":1,"msg":""}‘;
  8
  9 local CACHE_TIME_OUT = 10; --in second
 10
 11 local say = UPLOAD_FAIL;
 12
 13 function parseInput(agent)
 14     ret,errmsg = pcall(cjson.decode,agent);
 15     if ret then
 16         return errmsg;
 17     else
 18         return false;
 19     end
 20 end
 21
 22 function checkCache(key)
 23     if switcher == nil then
 24         return false;
 25     else
 26         cache = switcher:get(key)
 27         ret,errmsg = pcall(cjson.decode,cache);
 28         if ret then
 29             return errmsg;
 30         else
 31             return false;
 32         end
 33     end
 34 end
 35
 36 function check(input)
 37     appkey = input["arg0"];
 38     appvn = input["arg1"];
 39     if switcher == nil then
 40         ngx.log(ngx.INFO, "switcher nil");
 41         return false;
 42     else
 43         status = checkCache(appkey..appvn);
 44         if not status then
 45             ngx.log(ngx.INFO, "parse response failed");
 46             return false;
 47         else
 48             if status["lastmod"] == nil then
 49                 ngx.log(ngx.INFO, "lastmod nil");
 50                 return false;
 51             elseif status["lastmod"] < ( ngx.now() - CACHE_TIME_OUT ) then
 52                 ngx.log(ngx.INFO, "lastmod:"..status["lastmod"]..",outdated");
 53                 return false;
 54             else
 55                 return status["switch"];
 56             end
 57         end
 58     end
 59 end
 60
 61 function reload(arg0, arg1)
 62     response = ngx.location.capture("/switch_url");
 63     status = cjson.decode(response.body);
 64     result = {};
 65     result["switch"] = status["switch"];
 66     result["lastmod"] = ngx.now();
 67     switcher:set(arg0..arg1, cjson.encode(result));
 68     return status["switch"];
 69 end
 70
 71 function reply(result)
 72     if result == 0 then
 73         ngx.log(ngx.WARN, "it has been shut down");
 74         ngx.say(SHUT_DOWN);
 75     else
 76         request = {
 77             method = ngx.HTTP_POST,
 78             body = ngx.req.read_body(),
 79         }
 80         response = ngx.location.capture("real_url", request);
 81         ret,errmsg = pcall(cjson.decode,response.body);
 82         if ret then
 83             if "your_contidion" then
 84                 return UPLOAD_OK;
 85             else
 86                 return UPLOAD_FAIL;
 87             end
 88         else
 89             return UPLOAD_FAIL;
 90         end
 91     end
 92 end
 93
 94 --switch 0=off 1=on
 95 if agent == nil then
 96     --input empty
 97     ngx.say(say);
 98 else
 99     ngx.log(ngx.INFO, "agent:"..agent);
100     input = parseInput(agent);
101     if input then
102         --input correct
103         ngx.log(ngx.INFO, "input correct");
104         result = check(input)
105         if result == false then
106             --no cache or cache outdated, needs reload
107             ngx.log(ngx.INFO, "invalid cache, needs reload");
108             result = reload(input["arg0"],input["arg1"]);
109             say = reply(result);
110         else
111             --cache ok
112             ngx.log(ngx.INFO, "cache ok");
113             say = reply(result);
114         end
115     else
116         --input error
117         say = UPLOAD_FAIL;
118     end
119 end
120 ngx.log(ngx.INFO, "ngx says:"..say);
121 ngx.say(say);

上述代码实现了一个简单的高性能开关,每10秒从后端php加载一次开关状态(switch_url),根据请求的arg0和arg1来判断是不是要转发到real_url,从而保护真实服务不被流量冲击。在这里使用了nginx的共享内存。

在nginx的location里这样配置

lua_code_cache off; //开发的时候off,
set_form_input $name;
content_by_lua_file ‘conf/switch.lua‘;
error_log logs/pipedir/lua.log info;

在http配置里务必要记得配置共享内存

lua_shared_dict dict 10m;

性能测试:

nginx+lua:

php:800qps,就不上图了。。

一些个人感想:

看了一些帖子,都是通过lua直接访问redis获取白名单,或者是memcache,mysql,访问其他数据,个人觉得这样其实违背了系统设计的依赖关系,在lua中拼redis key很容易引发由高耦合引发的问题,例如拼错了key,但是怎么也找不到bug,因此我这里设计成了lua中通过ngx.location.capture访问现成的服务,相当于lua之依赖这个接口,实现了解耦

时间: 2024-12-22 09:52:32

nginx+lua打造10K qps+的web应用的相关文章

使用Nginx+Lua(OpenResty)开发高性能Web应用

在互联网公司,Nginx可以说是标配组件,但是主要场景还是负载均衡.反向代理.代理缓存.限流等场景:而把Nginx作为一个Web容器使用的还不是那么广泛.Nginx的高性能是大家公认的,而Nginx开发主要是以C/C++模块的形式进行,整体学习和开发成本偏高:如果有一种简单的语言来实现Web应用的开发,那么Nginx绝对是把好的瑞士军刀:目前Nginx团队也开始意识到这个问题,开发了nginxScript:可以在Nginx中使用JavaScript进行动态配置一些变量和动态脚本执行:而目前市面上

Nginx+Lua(OpenResty)开发高性能Web应用

使用Nginx+Lua(OpenResty)开发高性能Web应用 博客分类: 跟我学Nginx+Lua开发 架构 ngx_luaopenresty 在互联网公司,Nginx可以说是标配组件,但是主要场景还是负载均衡.反向代理.代理缓存.限流等场景:而把Nginx作为一个Web容器使用的还不是那么广泛.Nginx的高性能是大家公认的,而Nginx开发主要是以C/C++模块的形式进行,整体学习和开发成本偏高:如果有一种简单的语言来实现Web应用的开发,那么Nginx绝对是把好的瑞士军刀:目前Ngin

使用Nginx+Lua实现Web项目的灰度发布

使用Nginx+Lua实现Web项目的灰度发布 Nginx编译安装Lua模块 一.安装LUA环境及相关库 官方网站:https://github.com/openresty/lua-nginx-module 1.LuaJIT wget http://luajit.org/download/LuaJIT-2.0.2.tar.gz make && make install PREFIX=/usr/local/LuaJIT # vim /etc/profile export LUAJIT_LIB

安装OpenResty(Nginx+Lua)开发环境

一.简介 OpenResty? 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库.第三方模块以及大多数的依赖项.用于方便地搭建能够处理超高并发.扩展性极高的动态 Web 应用.Web 服务和动态网关. OpenResty? 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台.这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支

使用Nginx+Lua代理Hadoop HA

一.Hadoop HA的Web页面访问 Hadoop开启HA后,会同时存在两个Master组件提供服务,其中正在使用的组件称为Active,另一个作为备份称为Standby,例如HDFS的NameNode.YARN 的ResourceManager.HDFS的web页面只有通过Active的NameNode才能正常访问,同样地,YARN的web页面也只有通过Active的ResouceManager才能正常访问. (1) HDFS HA的Web访问 正常使用Nginx的proxy_pass代理单

深入浅出 nginx lua 为什么高性能

最近很多人问我nginx lua的优势是什么?为什么? 一.同步和异步.阻塞和非阻塞 如果要说清楚这个问题首先要了解:同步和异步.阻塞和非阻塞的关系 同步:php.java的正常代码都是同步执行的 异步:javascript的回调函数就是异步的 说白了自己写的程序里面如果没有回调函数都是同步的,常见的php.python等语言少有异步代码,当前大量使用异步代码的有javascript. 阻塞:查询数据库或者读写文件,如果没有获取结果就一直等待,说明是阻塞的.一般是阻塞的. 非阻塞:执行之后立刻返

nginx+lua+GraphicsMagick生成实时缩略图-CentOS7

背景 大多数的系统都会涉及缩略图的处理,比如新闻系统和电商系统,特别是电商系统,每个商品大图都会对应一系列尺寸的缩略图用于不同业务场景的使用.部分系统也会生成不同尺寸的缩略图以供PC.手机端.ipad端使用. 解决方案探索: 直接加载原图,使用css样式表来控制图片的宽高.显然不太合适,大家也尽量不要这样做. web程序在上传成功后,同时生成相应缩略图.这种做法效率较低,如果遇到批量导入的业务时严重影响性能.并且同步生成缩略图会占用一定量的存储空间,如果能按需生成岂不更好? 使用七牛.阿里云提供

使用Nginx+Lua实现waf

使用Nginx+Lua实现waf 软件包需求: 1 .Nginx兼容性[最后测试到1.13.6] wget http://nginx.org/download/nginx-1.13.6.tar.gz 2 .PCRE为Nginx编译安装关系的依赖 wget https://jaist.dl.sourceforge.net/project/pcre/pcre/8.42/pcre-8.42.tar.gz 3 .下载luajit解释器和ngx_devel_kit以及lua-nginx-module模块

Nginx+lua 实现调用.so文件

本文给大家分享的是Nginx结合lua 实现调用.so动态链接库文件的方法和示例,有需要的小伙伴可以参考下最近在和智能硬件部门一起,做一个室内定位的服务,该服务根据手机端传过来的beacon设备列表,根据一定的算法计算出具体的商场,并将商场ID和beason设备列表作为参数,调用.so文件中的计算方法,得出位置数据(坐标:x.y.z),返回给手机端. 因为服务对QPS要求比较高,并且都是纯查询操作,于是决定使用Nginx+lua+Redis的架构(该架构在公司内部已成主流,比较成熟).下面我将对