nginx+lua在我司的实践
导读:nginx是一个高性能的反向代理服务器,lua是一个小巧的脚本语言,这两个的巧妙结合会擦出怎样的火花呢。
关键词:nginx,lua,nginx+lua
前言
nginx,lua,nginx+lua,这三个名词不知道大家熟悉多少。为了后面内容的展示,我简单的介绍一下它们,想深入了解的网上资料很多,在这就不啰嗦了。nginx是一个高性能的反向代理服务器,一般会处在网站的最前端(有可能前面还会加一层slb,在这暂时忽略),用来做后端web服务的代理;lua是一个小巧的脚本语言,其设计的目的就是嵌入应用程序中,为其提供一些扩展和增强,比如redis,nginx等等;nginx+lua顾名思义就是nginx和lua的结合体,这两者之间沟通的桥梁是nginx_lua_module,它是nginx的一个模块,有了它nginx和lua才能互通,笔者在最近几年的工作中恰好有这方面的开发经验,所以想把这些真实的使用场景分享出来供大家做参考,具体的api doc还请大家参考官方资料。
案例1-入口层流量的灰度识别
何谓入口层流量的灰度识别呢,简单来说就是A用户的请求打到线上环境,B用户的请求打到灰度环境,目的就是做新功能的验证,实现逻辑很简单,大体流程如下:
1.测试同学在灰度控制台配置灰度规则,规则里会约束哪些url下哪些商户的请求进入灰度环境;
2.灰度控制台推送规则给入口层nginx,nginx会将规则存储到本地内存中,借助ngx.shared.DICT;
3.请求进入的时候(通过rewrite_by_lua_file触发)获取本地内存中的规则进行比对,如何命中规则就将请求转发到灰度环境,对nginx来说就是切换不同的upstream,比如线上是prod_serverA,灰度是gray_serverA;
代码片段如下:
upstream gray_serverA { server 192.68.1.1:8080; } upstream prod_serverA { server 192.68.1.2:8080; } server { listen 80; server_name graytest.demo.com; charset utf-8; location ~ \.do$ { set $backend 'prod_serverA'; #默认的upstream为线上服务 rewrite_by_lua_file "conf/lua-gray/rewriter.lua"; # rewrite_by_lua_file 可以简单的理解为一个过滤器,nginx在rewrite阶段会执行你指定的脚本文件, #在这个文件中我们会判断请求是否为灰度请求如果是灰度请求就将backend改为gray_serverA proxy_pass http://$backend; } }
案例2-入口层记录错误日志
之前有同学反馈说springmvc偶尔会报415错误,这个错误的一个常见原因是传递的Content-Type头不对,比如后端需要application/json,但是前端传递了application/x-www-form-urlencoded,那就会报这个错。但是跟前端确认了传递的头是没有问题的,有人猜测可能是头信息有特殊字符,导致后端web 容器(tomcat、resin)解析头出现了问题,既然这样把所有请求头打出来一看究竟,于是lua再一次出场,这次主要用来输出请求头到日志文件中,主要用到了log_by_lua_block这个指令,代码片段如下:
location ~ \.do$ { proxy_pass http://$backend; log_by_lua_block {
//判断下如果http 响应状态码为415就输出请求头到文件中 if tonumber(ngx.var.status) == 415 then ngx.log(ngx.ERR,"upstream reponse status is 415,please notice it,here are request headers:") local h, err = ngx.req.get_headers(100,true) if err == "truncated" then ngx.log(ngx.ERR,"request headers beyond 100,will not return") else local cjson = require("cjson") ngx.log(ngx.ERR,cjson.encode(h)) end end } }
想详细了解这个案例的,请移步到我的另一篇博客(https://www.cnblogs.com/chopper-poet/p/11625144.html)。
案例3-将nginx信息注册到监控平台
需求很简单,当nginx启动的时候将自身信息上报到redis中,上报内容包扣自身的ip,代理的域名信息等,有个监控平台会定期从redis读取这些信息做展示,方便运维干活,流程很简单就是定时读取自身ip和代理的域名信息写到redis中,为什么要定时呢,这里还起到一种心跳的效果,当长时间没有上报时可能是nginx出问题了,主要用到的指令如下:
1.init_worker_by_lua_file "conf/ua-grayngxReloadListener.lua",当nginx启动或者reload的时候会执行指定的文件;
2.ngx.worker.id() 这个方法会返回nginx worker进程的编号,从0开始,如果nginx有4个worker,那取值范围为0-3,这个主要是为了防止并发上报,因为第一步里面提到的init_worker_by_lua_file,如果有几个worker就会触发几次,所以为了只上报一次,会判断下返回值是否为0,也就是只让第一个worker执行;
3.ngx.timer.every 用来执行定时任务;
代码片段如下:
local workerId = ngx.worker.id() if(workerId == 0) then ngx.log(ngx.INFO,'workerId is 0 will startup task') local ok, err = ngx.timer.every(4,function() #1. get local ip and domins #2. write to redis end) else ngx.log(ngx.INFO,'workerId is not 0 ,just ignore it') end
案例4-将入口层流量同时转发到多个后端服务
类似于消息队列的发布订阅一样,在nginx这一层可以将一个请求同时发到多个地址,代码片段如下:
location ~ /capture_test$ { content_by_lua_block { ngx.req.read_body() res1, res3 = ngx.location.capture_multi{ { "/capture_test1",{ method = ngx.HTTP_POST, always_forward_body=true}}, { "/capture_test2",{ method = ngx.HTTP_POST, always_forward_body=true}}, } ngx.say(res3.body) ngx.exit(res3.status) }
总结
上面列举了nginx+lua在我司使用的四个案例,真实场景要复杂很多,为了方便大家理解,特意将案例做了简化。如果觉得有用,请点个推荐。
参考资料
https://github.com/openresty/lua-nginx-module
来我的公众号与我交流