nginx lua
lua协程机制:
lua协程机制和nginx协程类似:线程空间站内的一个执行单元,类似于线程,有自己独立的运行空间,其空间是基于用户态模拟出来的一个独立的运行空间,依托于线程,也要像线程一样被CPU去执行。好处就是在编写代码时候不用考虑异步方式,完全同步的去编写。一旦协程遇到阻塞,比如IO调用,它会主动到nginx的epoll模型上注册异步回调的句柄,放弃自己的执行权限。然后当epoll模型接收到了IO请求阻塞调用返回之后,再将对应的协程唤醒,返回时即获得CPU执行权限。
nginx lua插载点:
nginx留给我们许多lua开发的挂载点。能在nginx不同生命周期阶段来完成一些lua脚本插件式的加载,并且执行lua程序。
lua脚本可以挂载的点:
init_by_lua:系统启动时调用
init_worker_by_lua:worker进程启动时调用
set_by_lua:变量用复杂lua retrurn
rewrite_by_lua:重写url规则
access_by_lua:权限验证阶段
content_by_lua:内容输出节点
OpenResty框架:
OpenResty框架将nginx和nginx lua打包在一起,并提供了nginx lua的Redis,MemoryCache数据库等等这样的lua脚本库文件的封装。
协程机制:
依附于线程的内存模型,切换开销小,不会像线程切换一样耗CPU,内存
遇到阻塞及归还执行权,代码以同步方式来模拟一种异步的调用来编程
无需加锁
lua协程脚本:
function foo (a)
print("foo 函数输出",a)
return coroutine.yield(2*a) --返回2*a的值 yield:协程放弃权限,将权限归还给调用它的函数
end
co = coroutine.create(function(a,b) //create:创建协程
print("第一次协同程序执行输出",a,b) --co-body 1 10
lcoal r = foo(a+1)
print("第二次协同程序执行输出",r)
local r,s = coroutine.yield(a+b,a-b) --a,b的值为第一次调用协同程序时传入
print("第三次协同程序执行输出",r,s)
return b,"结束协同程序" --b的值为第二次调用协同程序时传入
end)
print("main",coroutine.resume(co,1,10)) --true,4 resume:恢复协程
print("--分割线--")
print("main",coroutine.resume(co,"r")) --true 11 -9
print("--分割线--")
print("main",coroutine.resume(co,"x","y")) --true 10 end
print("--分割线--")
print("main",coroutine.resume(co,"x","y")) --cannot resume dead coroutine
print("--分割线--")
nginx协程:
1.nginx的每一个Worker进程都是在epoll或kqueue这种事件模型之上,封装成协程。当某一个socket句柄接收到httpRequest请求时,epoll上的这个socket就会被唤醒,这时nginx会new出一个协程,这个协程将会去处理这个http请求的完整的生命周期,也就是将http请求做一些urlLocation的路由,accessController的鉴权,UpStream的反向代理等操作,然后在这些操作内,如果没有遇到IO阻塞的情况,这个协程处理完成就会返回一个httpResponse给nginx的epoll模型做socket的write的输出,一旦在这个过程中遇到block操作,对应协程就会放弃执行权限,并且将对应的和后端反向代理服务器的scoket连接注册到对应的epoll事件里,等待这个事件被response唤醒,一旦response被唤醒,nginx的Worker进程又会重新new一个协程出来,做之后的事情,因此,每一个操作其实都不会block对应的Worker进程自身的操作,都是依赖于epoll操作去做的,但是在整个串行链路上看上去就是几个协程共同串行在一起,完成一个请求的处理过程。
2.每一个请求都有一个协程进行处理。
3.即使nginx_lua必须要运行Lua,相对C有一定的开销,但依旧能保证高并发能力。
nginx协程机制:
1.nginx每个工作进程创建一个lua虚拟机,用来跑lua的脚本文件
2.工作进程内的所有协程共享同一个lua的虚拟机。
3.每一个外部请求由一个lua协程处理,之间数据隔离。
4.lua代码调用io等异步接口时,协程被挂起,上下文数据保持不变。一个httpRequest的请求进来后,被一个lua的协程代理,然后去执行相关操作,遇到io的阻塞,往epoll模型上注册完,自己挂起,等待epoll模型收到数据返回之后唤醒自己,执行下面操作,因此他们之间的数据是隔离的。
5.自动保存,不阻塞工作进程
6.io异步操作完成后还原协程上下文,代码继续执行。
nginx协程机制和java多线程Servlet模型的区别:
一个httpRequest的请求进来后,被一个lua的协程代理,然后去执行相关操作,遇到io的阻塞,往epoll模型上注册完,自己挂起,等待epoll模型收到数据返回之后唤醒自己,执行下面操作,因此他们之间的数据是隔离的。这也就是nginx处理http请求和javaServlet处理http请求完全不一样的模型。javaServlet是一个请求对应一个线程去处理的,但是nginx内是单Worker进程内是一个单线程的模型,因此他对每一个http请求完全是基于协程的方式串行处理,一旦遇到阻塞,通过io多路复用的epoll模型去解决,但是java内部遇到阻塞就被动在等,因为本身就是多线程的模型,因此相对对于这些模型来说,nginx协程机制是比java虚拟机多线程Servlet模型机制更加高效。
nginx处理阶段:
NGX_HTTP_POST_READ_PHASE = 0 //读取请求头
NGX_HTTP_SERVER_REWRITE_PHASE //执行rewrite -》rewrite_handler
NGX_HTTP_FIND_CONFIG_PHASE //根据uri替换location
NGX_HTTP_REWRITE_PHASE //根据替换结果继续执行rewrite -》rewrite_handler
NGX_HTTP_POST_REWRITE_PHASE //执行rewrite后处理
NGX_HTTP_PREACCESS_PHASE //认证预处理 请求限制,连接限制 -》limit_conn_handerlimit_req_handler
NGX_HTTP_ACCESS_PHASE //认证处理 -》auth_basic_handler,access_handler
NGX_HTTP_POST_ACCESS_PHASE //认证后处理,认证不通过,丢包
NGX_HTTP_TRY_FILES_PHASE //尝试try标签
NGX_HTTP_CONTENT_PHASE //内容处理- 》static_handler
NGX_HTTP_LOG_PHASE //日志处理 -》 log_handler
nginx lua实战
在openresty文件下创建lua文件夹,把所有的lua脚本放入这个文件夹内。我们一般使用content_by_lua这个挂载点,来修改对应location某一个固定的url所产生的应用的lua编程:
修改nginx配置文件:
vim conf/nginx.conf
//加入新的location,配置staticitem/get
location /staticitem/get{
default_type "text/html"; //不指定格式会以文件形式输出
content_by_lua_file ../lua/staticitem.lua; //配置文件地址
}
进入lua文件夹:
vim staticitem.lua
ngx.say("heelo static item lua"); //以httpResponse方式返回这句
重启nginx配置:
sbin/nginx -s reload
测试:
openResty
openResty由Nginx核心加很多第三方模块组成,默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用。
借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序。
openResty提供了大量组件如Mysql,Redis,Memcached等等。使在Nginx上开发Web应用简单。
OpenResty实战
1.openresty hello world
进入openresty/lua,编写helleworld文件:
vim helloworld.lua
ngx.exec("/item/get?id=6"); //商品详情页的访问
进入openresty/nginx,修改conf文件:
vim conf/nginx.conf
location /helloworld{
content_by_lua_file ../lua/helloworld.lua;
}
重启nginx配置:
sbin/nginx -s reload
测试访问helloworld:
2.shared dic:共享内存字典,所有的worker进程可见,lru淘汰(类似与guava cache)
在nginx配置文件中配置shared dic:
vim conf/nginx.conf
lua_shared_dict my_cache 128m; //指定名称和开辟大小
在lua文件中编写itemSharedic文件:
vim itemsharedic.lua
function get_from_cache(key)
local cache_ngx = ngx.shared.my_cache //定义local变量取自my_cache中
local value = cache_ngx:get(key) //获取value
return value
end
function set_tocache(key,value,exptime)
if not exptime then
exptime = 0;
end
local cache_ngx = ngx.shared.my_cache
local succ,err,forcible = cache_ngx:set(jey,value,exptime) //定义返回值
retrun succ
end
local args = ngx.req.get_uri_args() //可以拿到对应Request请求上的一个参数
local id = args["id"]
local item_model = get_from_cache("item_"..id) //".."相当于拼接字符串
if item_model == uil the n
local resp = ngx.location.capture("/item/get?id="..id) //缓存请求不到时转发给后台
item_model = resp.body
set_to_cache("item_"..id,item_model,1*60) //缓存1min
end
ngx.say(item_model)
编辑nginx.conf文件:
vim conf/nginx.conf
server{
location /luaitem/get{
default_type "applicatoin/json";
content_by_lua_file ../lua/itemsharedic.lua //将luaitem/get的请求方式代理到了../lua/itemsharedic.lua这个lua脚本上
}
}
重启nginx配置:
sbin/nginx -s reload
查看启动日志:
进入ngixn的logs目录执行:
tail -f error.log
测试:
验证nginx收到请求:
进入nginxlogs文件下执行:
tail -f access.log
可以看到请求
但是在项目服务器中的tomcat文件中查看日志:
tail -f access_log.yyyy-mm-dd.log
可以看到没有请求到达。
进行jmeter压测:
以之前设置的并发参数请求,可以看出TPS可达3700,平均响应页很低
使用top -H也可以看到压力主要在nginx服务器上,程序服务器在1min内压力并不大。
3.openresty redis支持
架构图:
可以避免部分脏读的影响。
进入lua目录:
vim itemredis.lua
local args = ngx.req.get_uri_args()
local id = args["id"]
local redis = require "resty.redis"
local cache = redis.new()
local ok,err = cache:connect("ip",port)
local item_model = cache:get("item_"..id)
if item_model == ngx.null or item_model == nil then
local resp = ngx.location.capture("/item/get?id="..id)
item_model = resp.body
end
返回上层修改nginx/conf/nginx,conf
server{
location /luaitem/get{
default_type "application/json";
content_by_lua_life ../lua/itemredis.lua;
}
}
重启nginx配置:
sbin/nginx -s reload