阻塞操作
Openresty 的诞生,一直对外宣传是非阻塞( 100% noblock)的。基于事件通知的 Nginx 给我们带来了足够强悍的高并发支持,但是也对我们的编码有特殊要求。这个特殊要求就是我们的代码,也必须是非阻塞的。如果你的服务端编程生涯一开始就是从异步框架开始的,恭喜你了。但如果你的编程生涯是从同步框架过来的,而且又是刚刚开始深入了解异步框架,那你就要小心了。
Nginx 为了减少系统上下文切换,它的 worker 是用单进程单线程设计的,事实证明这种做法运行效率很高。 Nginx 要么是在等待网络讯号,要么就是在处理业务(请求数据解析、过滤、内容应答等),没有任何额外资源消耗。
常见语言代表异步框架
- Golang :使用协程技术实现
- Python :gevent 基于协程的 Python 网络库
- Rust :用的少,只知道语言完备支持异步框架
- Openresty :基于 Nginx ,使用事件通知机制
- Java : Netty ,使用网络事件通知机制
异步编程的噩梦
异步编程,如果从零开始,难度是非常大的。一个完整的请求,由于网络传输的非连续性,这个请求要被多次挂起、恢复、运行,一旦网络有新数据到达,都需要立刻唤醒恢复原始请求处于运行状态。开发人员不仅仅要考虑异步 api 接口本身的使用规范,还要考虑业务会话的完整处理,稍有不慎,全盘皆输。
最最重要的噩梦是,我们好不容易搞定异步框架和业务会话完整性,但是却在我们的业务会话上使用了阻塞函数。一开始没有任何感知,只有做压力测试的时候才发现我们的并发量上不去,各种卡曼顿,甚至开始怀疑人生:异步世界也就这样。
Openresty 中的阻塞函数
官方有明确说明, Openresty 的官方 API 绝对 100% noblock ,所以我们只能在她的外面寻找了。我这里大致归纳总结了一下,包含下面几种情况:
- 高 CPU 的调用(压缩、解压缩、加解密等)
- 高磁盘的调用(所有文件操作)
- 非 Openresty 提供的网络操作( luasocket 等)
- 系统命令行调用(
os.execute
等)
这些都应该是我们尽量要避免的。理想丰满,现实骨感,谁能保证我们的应用中不使用这些类型的 API ?没人保证,我们能做的就是把他们的调用数量、频率降低再降低,如果还是不能满足我们需要,那么就考虑把他们封装成独立服务,对外提供 TCP/HTTP 级别的接口调用,这样我们的 Openresty 就可以同时享受异步编程的好处又能达到我们的目的。