OpenResty性能的保证 —— 非阻塞IO
在文件开始时,首先要说的是,在OpenResty编程里的原则是非阻塞IO优先。 OpenResty能有如此高性能的表现是因为基于Nginx的事件响应机制和Lua的协程机制。在请求的处理流里,若使用了阻塞式IO
而不使用如cosocket
的非阻塞式方法来处理IO,那LuaJIT是不会把控制权交回给Nginx的事件循环(Event Loop),这就导致了
Nginx里的其他请求只能排队干等着前一个请求的IO处理完毕后才能被处理。以下介绍一些会导致性能下降的写法。
执行外部命令
关闭进程
如使用 os.execute(" cp test.exe /tmp ")
,该方法会阻塞当前请求的处理,该如何避免?有两种方式:
1.使用FFI库里的方法代替
比如当要kill一个进程时,可以使用:
1 | local resty_signal = require "resty.signal" |
在涉及到对图片,加解密等CPU密集型的操作时,可以先查阅下FFI库里有无封装了对应方法。
2.使用基于nginx.pipe
的 lua-resty-shell
库
你也可以在非阻塞操作shell.run
里运行命令:
1 | $ resty -e 'local shell = require "resty.shell" |
磁盘IO
下面看下处理磁盘IO的场景,读取配置文件是服务端很常见的操作:
1 | local path = "/conf/apisix.conf" |
当然,配置读取只在init
初始化时进行一次,而不会在每个请求进来时都进行。但是如果需要在请求里操作磁盘IO,
则需认真考虑如何解决了。
首先可以使用第三方的C模块lua-io-nginx-module
,该模块为OpenResty提供了非阻塞IO的Lua API,但是你
不能像使用cosocket
那样使用它,因为磁盘IO的耗时操作并没有消失,只是换一种方式进行而已。它利用Nginx的
线程池机制的优势,把磁盘IO的操作从主线程移到其他线程进行,所以主线程不会被磁盘IO所阻塞。
如果要使用该模块,因为它是C模块,所以需要重新编译Nginx,然后像使用普通Lua库那样使用即可。
1 | local ngx_io = require "ngx.io" |
还有一种方式,就是不把数据读/写到磁盘,例如在写日志时:
ngx.log(ngx.WARN, "info")
这好像很正常,但是不能经常调用,因为首先它是较昂贵的函数调用,其次即便有了缓冲,大量且频繁的磁盘写入很影响性能。
那如何处理需要这种写入日志的问题呢?
可以尝试把日志通过cosocket
的非阻塞网络调用传输到远程日志服务上。使用lua-resty-logger-socket
来实现:
1 | local logger = require "resty.logger.socket" |
以上两种方式都是为了避免主线程阻塞,一种是放到其他线程处理,一种是通过网络交给远程服务处理。
luasocket
luasocket
经常与OpenResty提供的cosocket
混淆,luasocket
也能执行网络通讯的功能,但是它不能
以非阻塞的方式进行,所以使用它会导致性能很差。
当然luasocket
也有它的应用场景,在一些无法使用cosocket
的阶段,通过会使用ngx.timer来规避,当然
也可以在init_by_lua
、init_worker_by_lua*
这种一次性阶段的地方使用luasocket
来实现cosocket
的功能。另外lua-resty-socket
是一个兼容luasocket
和cosocket
的二次封装开源库,它内容值得深入学习,
如果有兴趣,可参考这里
总结
总的来说,在OpenResty里,意识到阻塞IO操作的类型和对应的非阻塞解决方案是写出高性能程序的基础。