理解Openresty协程
文章目录
如果在log_by_lua_block
阶段放上死循环代码会咋样?
|
|
查阅资料话你可能会看到这么一句解释:
log Phase这个阶段,就是openresty能处理的最后阶段。到这个阶段的时候,实际上请求的响应已经发送给客户端了。另外我也测试过了,即使在这个阶段发生了错误,如 io 错误,也不会影响接口的正常响应,所以使用 log_by_lua 很是符合需求。
看到这里你是不是会觉得对上边的问题已经有答案了呢?
起初我片面的以为log phase是一个请求的最后一个阶段就可以放飞了,不用太关注性能和耗时问题。
这当然是不对的,要搞清楚它的话就得理解Nginx的事件处理和Lua协程机制。
OpenResty架构
OpenResty的Master和Worker进程中都包含一个LuaJIT 虚拟机,
- 有请求过来时,通过抢占锁,由一个worker进程来跟进处理;
- worker进程内部会创建一个lua协程并与该请求绑定,也就是说一个请求对应一个lua协程;
因为worker是单线程的,在同一时间每个worker只能有一个占用CPU时间正在运行着的Lua协程,因此同一时刻每个worker只能处理一个请求。 这时你可能会有疑问了:既然同一个时刻只能处理一个请求,那nginx又是如何支持高并发的呢? 实际上Nginx是通过epoll事件驱动机制,当需要等待io时,协程主动让出cpu让其它协程占用cpu运行,继续处理其它请求,因为足够快话看起来就像并发的一样。不同于其它的多线程模式,一个请求对应一个线程,在高并发的情况占用资源也少很多。
OpenResty协程类型
在OpenResty层面,Lua协程和Nginx事件机制相互配合,当lua需要等待io时主动yield
让出cpu并添加一个event到Nginx,等待io满足后再resume
继续执行。
到此我们也能看出,无论在哪个Phase lua程序都必须尽可能的快,更不能有阻塞操作,否则就会影响吞吐量。
OpenResty协程类型
- 全局主协程
- 请求子协程
- 用户创建的"Light threads"
它们的调度关系可以参考下面这张图
使用 ngx.exit
, ngx.exec
, ngx.redirect
可以直接跳出协程,而不用等待子协程处理完成。
|
|
返回值说明
- NGX_DONE 处理告一段落(子协程完成),还有协程(
light thread
)被挂起 - NGX_AGIN 子协程未结束,继续等待下次唤醒重入
- NGX_OK 当前阶段处理完成(子协程和运行在内的所有
light thread
都结束)
总结
OpenResty将Nginx的事件驱动机制与Lua结合,实现了以同步的方式写代码而有异步功能的效果,又因为是单线程,也不用担心并发时竞争问题,极大的提升了开发效率。 不过需要注意的是,协程并抢占式,因此写代码时要避免阻塞和尽快完成,否则会长期占用CPU而不能处理后续请求,影响整个吞吐量。
参考
https://time.geekbang.org/column/article/102113
https://github.com/jinhailang/blog/issues/26
https://github.com/jinhailang/blog/issues/21
https://github.com/jinhailang/blog/issues/32
https://my.oschina.net/u/2539854/blog/853012
https://groups.google.com/g/openresty/c/2UXtJHvSpXM/m/ZyLdtKwBAAAJ?pli=1
https://cloud.tencent.com/developer/article/1904796
https://forum.openresty.us/d/3375-e77210525689e3d3753440fe8db56d97/4
文章作者 XniLe
上次更新 2023-10-19