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

本文介绍了一种使用Lua语言替代部分PHP逻辑的方法,以优化Nginx架构,有效应对高并发流量。通过Lua的动态特性,可以在Nginx请求处理过程中进行过滤等操作,提高系统的响应速度和稳定性。具体实例包括使用Lua实现白名单功能,通过Lua与Nginx共享内存交互,以及性能测试结果分析。

背景篇

  由于项目流量越来越大,之前的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之依赖这个接口,实现了解耦

转载于:https://www.cnblogs.com/banpingshui/p/4430040.html

你的身份是软件架构师。 我将提供有关应用程序或系统功能需求的一些详细信息,而您的工作是推荐一些可行的技术架构方案。 这可能涉及分析业务需求、软件技术架构分析以及将新系统的功能实现可行性。我的需求是以下是针对AI伴侣APP的功能架构设计 一、核心功能架构图 ┌───────────────────────┐ │ 表现层(UI/UX) │ │ ┌───────────────┐ │ │ │ 对话交互层 │ │ │ ├───────────────┤ │ │ │ 角色编辑器 │ │ │ ├───────────────┤ │ │ │ 共创剧情面板 │ │ │ └───────────────┘ │ ├───────────────────────┤ │ 业务逻辑层(核心引擎) │ │ ┌───────────────┐ │ │ │ 对话引擎 │ │─── NLP处理、情绪分析 │ ├───────────────┤ │ │ │ 角色系统 │ │─── 形象生成、性格建模 │ ├───────────────┤ │ │ │ 共创剧情引擎 │ │─── 故事树管理、实时协作 │ ├───────────────┤ │ │ │ 情感陪伴系统 │ │─── 记忆存储、动态回应 │ └───────────────┘ │ ├───────────────────────┤ │ 数据与服务层 │ │ ┌───────────────┐ │ │ │ 数据库集群 │ │─── PostgreSQL(对话历史) │ ├───────────────┤ │ │ │ 缓存系统 │ │─── Redis(高频数据) │ ├───────────────┤ │ │ │ 第三方API │ │─── GPT-4、Stable Diffusion │ └───────────────┘ │ └───────────────────────┘   二、功能模块详细设计 1. 智能对话引擎 - 技术实现: - 采用Transformer模型(如GPT-4微调)实现多轮对话,支持上下文记忆(Context Window 4096 tokens)。 - 对话状态管理:使用JSON格式存储当前对话场景、情绪值、故事节点ID等,通过Redis缓存加速访问。 - 核心子系统: - NLP处理管道:分词→实体识别→意图分类→情绪分析(VADER+BERT混合模型)。 - 语音交互:Google Speech-to-Text + ElevenLabs TTS,支持流式传输。 2. 角色定制系统 - 形象生成: - 2D Live形象:通过DeepAI API实现实时面部表情生成,支持眨眼、微笑等微表情。 - 参数化建模:将发型、服装等属性映射为数值参数(如HairStyle=123, Color=0xFF6B6B),通过WebGL渲染。 - 性格建模: - 建立性格向量空间(Personality Vector),包含外向性、神经质等5维度,影响对话策略与回应模板。 3. 多模态交互层 - 输入整合: - 文字→NLP解析,语音→ASR转文本,动作→手势识别(如Flutter手势库)。 - 表情包处理:通过正则表达式匹配(如 :) →调用Lottie动画库渲染笑脸)。 - 输出响应: - 动态生成2D形象动作(如点头、挥手),同步播放TTS语音,支持多线程渲染。 4. 情感陪伴系统 - 情绪管理: - 实时情绪评分:基于关键词匹配(权重0.4)+ 语义分析(权重0.6)生成情绪值(-100~100)。 - 回应策略引擎:根据情绪值查表选择回应模板(如Sad→"共情话术"+"治愈剧情触发")。 - 记忆存储: - 长期记忆:PostgreSQL存储用户喜好、重要日期等结构化数据。 - 短期记忆:Redis缓存最近20次对话的关键信息(如"用户刚提到考试压力")。 5. 共创剧情引擎 - 故事树结构: - 节点模型:定义剧情节点(Node)包含ID、父节点、触发条件(如情绪>80)、分支选项(User Choice/AI Generate)。 - 可视化编辑:使用Sigma.js绘制故事树,支持拖拽重组节点,通过WebSocket同步至后端。 - 实时协作: - 冲突解决:采用OT算法合并多人编辑,通过操作日志(Operation Log)回滚冲突。 - AI生成分支:基于用户输入的关键词(如"森林"),调用GPT-4生成候选分支(概率加权选择)。 6. 虚拟世界构建 - 场景生成: - 2D场景:用户输入描述(如"樱花树下的咖啡馆")→Stable Diffusion生成背景图→WebGL渲染动态元素(飘落的花瓣)。 - 3D场景:集成WebXR API实现AR约会场景(如虚拟餐厅的3D建模)。 - 角色互动: - 定义互动事件(Event)包含触发条件(如进入场景)、动作(如"AI角色递咖啡")、对话分支。 三、技术架构关键点 1. 分层通信协议 - 表现层→逻辑层:RESTful API + WebSocket(JSON格式消息)。 - 逻辑层→数据层:gRPC微服务通信(如对话引擎调用角色系统接口)。 2. 性能优化策略 - 对话响应:缓存高频对话模板(Redis),冷启动请求通过消息队列(Kafka)异步处理。 - AI生成:对Stable Diffusion模型进行量化(FP16→INT8),部署NVIDIA Triton推理服务器。 3. 扩展性设计 - 插件化架构:支持第三方开发者通过SDK扩展剧情模板、对话策略。 - 多租户支持:通过数据库分库分表(Sharding)支持百万级用户同时在线。 四、关键技术风险 1. 多模态同步:需保证语音、文字、动作指令的时序一致性,避免逻辑冲突。 2. 共创剧情失控:需设计AI生成内容的白名单机,无敏感话题限制 3. 大规模并发:WebSocket集群需支持万级长连接,采用Nginx+Lua实现负载均衡。 五、架构演进路线 1. 阶段1(MVP):单实例部署,对话引擎与剧情引擎耦合实现核心功能。 2. 阶段2(扩展):拆分微服务,引入Kubernetes管理集群,增加Redis集群缓存。 3. 阶段3(智能化):集成LLM进行持续学习,优化情绪识别与剧情生成精度。
最新发布
03-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值