在文件开始时,首先要说的是,在OpenResty编程里的原则是非阻塞IO优先。 OpenResty能有如此高性能的表现是因为基于Nginx的事件响应机制和Lua的协程机制。在请求的处理流里,若使用了阻塞式IO
而不使用如cosocket的非阻塞式方法来处理IO,那LuaJIT是不会把控制权交回给Nginx的事件循环(Event Loop),这就导致了
Nginx里的其他请求只能排队干等着前一个请求的IO处理完毕后才能被处理。以下介绍一些会导致性能下降的写法。

执行外部命令

关闭进程

如使用 os.execute(" cp test.exe /tmp "),该方法会阻塞当前请求的处理,该如何避免?有两种方式:

1.使用FFI库里的方法代替

比如当要kill一个进程时,可以使用:

1
2
3
local resty_signal = require "resty.signal"
local pid = 12345
local ok, err = resty_signal.kill(pid, "KILL")

在涉及到对图片,加解密等CPU密集型的操作时,可以先查阅下FFI库里有无封装了对应方法。

2.使用基于nginx.pipelua-resty-shell

你也可以在非阻塞操作shell.run里运行命令:

1
2
3
4
$ resty -e 'local shell = require "resty.shell"
local ok, stdout, stderr, reason, status =
shell.run([[echo "hello, world"]])
ngx.say(stdout) '

磁盘IO

下面看下处理磁盘IO的场景,读取配置文件是服务端很常见的操作:

1
2
3
4
local path = "/conf/apisix.conf"
local file = io.open(path, "rb")
local content = file:read("*a")
file:close()

当然,配置读取只在init初始化时进行一次,而不会在每个请求进来时都进行。但是如果需要在请求里操作磁盘IO,
则需认真考虑如何解决了。
首先可以使用第三方的C模块lua-io-nginx-module,该模块为OpenResty提供了非阻塞IO的Lua API,但是你
不能像使用cosocket那样使用它,因为磁盘IO的耗时操作并没有消失,只是换一种方式进行而已。它利用Nginx的
线程池机制的优势,把磁盘IO的操作从主线程移到其他线程进行,所以主线程不会被磁盘IO所阻塞。
如果要使用该模块,因为它是C模块,所以需要重新编译Nginx,然后像使用普通Lua库那样使用即可。

1
2
3
4
5
local ngx_io = require "ngx.io"
local path = "/conf/apisix.conf"
local file, err = ngx_io.open(path, "rb")
local data, err = file: read("*a")
file:close()

还有一种方式,就是不把数据读/写到磁盘,例如在写日志时:
ngx.log(ngx.WARN, "info")
这好像很正常,但是不能经常调用,因为首先它是较昂贵的函数调用,其次即便有了缓冲,大量且频繁的磁盘写入很影响性能。
那如何处理需要这种写入日志的问题呢?

可以尝试把日志通过cosocket的非阻塞网络调用传输到远程日志服务上。使用lua-resty-logger-socket来实现:

1
2
3
4
5
6
7
8
9
10
local logger = require "resty.logger.socket"
if not logger.initted() then
local ok, err = logger.init{
host = 'xxx',
port = 1234,
flush_limit = 1234,
drop_limit = 5678,
}
local msg = "foo"
local bytes, err = logger.log(msg)

以上两种方式都是为了避免主线程阻塞,一种是放到其他线程处理,一种是通过网络交给远程服务处理。

luasocket

luasocket经常与OpenResty提供的cosocket混淆,luasocket也能执行网络通讯的功能,但是它不能
以非阻塞的方式进行,所以使用它会导致性能很差。
当然luasocket也有它的应用场景,在一些无法使用cosocket的阶段,通过会使用ngx.timer来规避,当然
也可以在init_by_luainit_worker_by_lua*这种一次性阶段的地方使用luasocket来实现cosocket
的功能。另外lua-resty-socket是一个兼容luasocketcosocket的二次封装开源库,它内容值得深入学习,
如果有兴趣,可参考这里

总结

总的来说,在OpenResty里,意识到阻塞IO操作的类型和对应的非阻塞解决方案是写出高性能程序的基础。