<audio id="audio" title="35 | OpenResty：更灵活的Web服务器" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7d/68/7d395f67094c2bfa140ee2d100996168.mp3"></audio>

在上一讲里，我们看到了高性能的Web服务器Nginx，它资源占用少，处理能力高，是搭建网站的首选。

虽然Nginx成为了Web服务器领域无可争议的“王者”，但它也并不是没有缺点的，毕竟它已经15岁了。

“一个人很难超越时代，而时代却可以轻易超越所有人”，Nginx当初设计时针对的应用场景已经发生了变化，它的一些缺点也就暴露出来了。

Nginx的服务管理思路延续了当时的流行做法，使用磁盘上的静态配置文件，所以每次修改后必须重启才能生效。

这在业务频繁变动的时候是非常致命的（例如流行的微服务架构），特别是对于拥有成千上万台服务器的网站来说，仅仅增加或者删除一行配置就要分发、重启所有的机器，对运维是一个非常大的挑战，要耗费很多的时间和精力，成本很高，很不灵活，难以“随需应变”。

那么，有没有这样的一个Web服务器，它有Nginx的优点却没有Nginx的缺点，既轻量级、高性能，又灵活、可动态配置呢？

这就是我今天要说的OpenResty，它是一个“更好更灵活的Nginx”。

## OpenResty是什么？

其实你对OpenResty并不陌生，这个专栏的实验环境就是用OpenResty搭建的，这么多节课程下来，你应该或多或少对它有了一些印象吧。

OpenResty诞生于2009年，到现在刚好满10周岁。它的创造者是当时就职于某宝的“神级”程序员**章亦春**，网名叫“agentzh”。

OpenResty并不是一个全新的Web服务器，而是基于Nginx，它利用了Nginx模块化、可扩展的特性，开发了一系列的增强模块，并把它们打包整合，形成了一个**“一站式”的Web开发平台**。

虽然OpenResty的核心是Nginx，但它又超越了Nginx，关键就在于其中的ngx_lua模块，把小巧灵活的Lua语言嵌入了Nginx，可以用脚本的方式操作Nginx内部的进程、多路复用、阶段式处理等各种构件。

脚本语言的好处你一定知道，它不需要编译，随写随执行，这就免去了C语言编写模块漫长的开发周期。而且OpenResty还把Lua自身的协程与Nginx的事件机制完美结合在一起，优雅地实现了许多其他语言所没有的“**同步非阻塞**”编程范式，能够轻松开发出高性能的Web应用。

目前OpenResty有两个分支，分别是开源、免费的“OpenResty”和闭源、商业产品的“OpenResty+”，运作方式有社区支持、OpenResty基金会、OpenResty.Inc公司，还有其他的一些外界赞助（例如Kong、CloudFlare），正在蓬勃发展。

<img src="https://static001.geekbang.org/resource/image/9f/01/9f7b79c43c476890f03c2c716a20f301.png" alt="unpreview">

顺便说一下OpenResty的官方logo，是一只展翅飞翔的海鸥，选择海鸥是因为“鸥”与OpenResty的发音相同。另外，这个logo的形状也像是左手比出的一个“OK”姿势，正好也是一个“O”。

## 动态的Lua

刚才说了，OpenResty里的一个关键模块是ngx_lua，它为Nginx引入了脚本语言Lua。

Lua是一个比较“小众”的语言，虽然历史比较悠久，但名气却没有PHP、Python、JavaScript大，这主要与它的自身定位有关。

<img src="https://static001.geekbang.org/resource/image/4f/d5/4f24aa3f53969b71baaf7d9c7cf68fd5.png" alt="unpreview">

Lua的设计目标是嵌入到其他应用程序里运行，为其他编程语言带来“脚本化”能力，所以它的“个头”比较小，功能集有限，不追求“大而全”，而是“小而美”，大多数时间都“隐匿”在其他应用程序的后面，是“无名英雄”。

你或许玩过或者听说过《魔兽世界》《愤怒的小鸟》吧，它们就在内部嵌入了Lua，使用Lua来调用底层接口，充当“胶水语言”（glue language），编写游戏逻辑脚本，提高开发效率。

OpenResty选择Lua作为“工作语言”也是基于同样的考虑。因为Nginx C开发实在是太麻烦了，限制了Nginx的真正实力。而Lua作为“最快的脚本语言”恰好可以成为Nginx的完美搭档，既可以简化开发，性能上又不会有太多的损耗。

作为脚本语言，Lua还有一个重要的“**代码热加载**”特性，不需要重启进程，就能够从磁盘、Redis或者任何其他地方加载数据，随时替换内存里的代码片段。这就带来了“**动态配置**”，让OpenResty能够永不停机，在微秒、毫秒级别实现配置和业务逻辑的实时更新，比起Nginx秒级的重启是一个极大的进步。

你可以看一下实验环境的“www/lua”目录，里面存放了我写的一些测试HTTP特性的Lua脚本，代码都非常简单易懂，就像是普通的英语“阅读理解”，这也是Lua的另一个优势：易学习、易上手。

## 高效率的Lua

OpenResty能够高效运行的一大“秘技”是它的“**同步非阻塞**”编程范式，如果你要开发OpenResty应用就必须时刻铭记于心。

“同步非阻塞”本质上还是一种“**多路复用**”，我拿上一讲的Nginx epoll来对比解释一下。

epoll是操作系统级别的“多路复用”，运行在内核空间。而OpenResty的“同步非阻塞”则是基于Lua内建的“**协程**”，是应用程序级别的“多路复用”，运行在用户空间，所以它的资源消耗要更少。

OpenResty里每一段Lua程序都由协程来调度运行。和Linux的epoll一样，每当可能发生阻塞的时候“协程”就会立刻切换出去，执行其他的程序。这样单个处理流程是“阻塞”的，但整个OpenResty却是“非阻塞的”，多个程序都“复用”在一个Lua虚拟机里运行。

<img src="https://static001.geekbang.org/resource/image/9f/c6/9fc3df52df7d6c11aa02b8013f8e9bc6.png" alt="">

下面的代码是一个简单的例子，读取POST发送的body数据，然后再发回客户端：

```
ngx.req.read_body()                  -- 同步非阻塞(1)

local data = ngx.req.get_body_data()
if data then
    ngx.print(&quot;body: &quot;, data)        -- 同步非阻塞(2)
end

```

代码中的“ngx.req.read_body”和“ngx.print”分别是数据的收发动作，只有收到数据才能发送数据，所以是“同步”的。

但即使因为网络原因没收到或者发不出去，OpenResty也不会在这里阻塞“干等着”，而是做个“记号”，把等待的这段CPU时间用来处理其他的请求，等网络可读或者可写时再“回来”接着运行。

假设收发数据的等待时间是10毫秒，而真正CPU处理的时间是0.1毫秒，那么OpenResty就可以在这10毫秒内同时处理100个请求，而不是把这100个请求阻塞排队，用1000毫秒来处理。

除了“同步非阻塞”，OpenResty还选用了**LuaJIT**作为Lua语言的“运行时（Runtime）”，进一步“挖潜增效”。

LuaJIT是一个高效的Lua虚拟机，支持JIT（Just In Time）技术，可以把Lua代码即时编译成“本地机器码”，这样就消除了脚本语言解释运行的劣势，让Lua脚本跑得和原生C代码一样快。

另外，LuaJIT还为Lua语言添加了一些特别的增强，比如二进制位运算库bit，内存优化库table，还有FFI（Foreign Function Interface），让Lua直接调用底层C函数，比原生的压栈调用快很多。

## 阶段式处理

和Nginx一样，OpenResty也使用“流水线”来处理HTTP请求，底层的运行基础是Nginx的“阶段式处理”，但它又有自己的特色。

Nginx的“流水线”是由一个个C模块组成的，只能在静态文件里配置，开发困难，配置麻烦（相对而言）。而OpenResty的“流水线”则是由一个个的Lua脚本组成的，不仅可以从磁盘上加载，也可以从Redis、MySQL里加载，而且编写、调试的过程非常方便快捷。

下面我画了一张图，列出了OpenResty的阶段，比起Nginx，OpenResty的阶段更注重对HTTP请求响应报文的加工和处理。

<img src="https://static001.geekbang.org/resource/image/36/df/3689312a970bae0e949b017ad45438df.png" alt="">

OpenResty里有几个阶段与Nginx是相同的，比如rewrite、access、content、filter，这些都是标准的HTTP处理。

在这几个阶段里可以用“xxx_by_lua”指令嵌入Lua代码，执行重定向跳转、访问控制、产生响应、负载均衡、过滤报文等功能。因为Lua的脚本语言特性，不用考虑内存分配、资源回收释放等底层的细节问题，可以专注于编写非常复杂的业务逻辑，比C模块的开发效率高很多，即易于扩展又易于维护。

OpenResty里还有两个不同于Nginx的特殊阶段。

一个是“**init阶段**”，它又分成“master init”和“worker init”，在master进程和worker进程启动的时候运行。这个阶段还没有开始提供服务，所以慢一点也没关系，可以调用一些阻塞的接口初始化服务器，比如读取磁盘、MySQL，加载黑白名单或者数据模型，然后放进共享内存里供运行时使用。

另一个是“**ssl阶段**”，这算得上是OpenResty的一大创举，可以在TLS握手时动态加载证书，或者发送“OCSP Stapling”。

还记得[第29讲](https://time.geekbang.org/column/article/111940)里说的“SNI扩展”吗？Nginx可以依据“服务器名称指示”来选择证书实现HTTPS虚拟主机，但静态配置很不灵活，要编写很多雷同的配置块。虽然后来Nginx增加了变量支持，但它每次握手都要读磁盘，效率很低。

而在OpenResty里就可以使用指令“ssl_certificate_by_lua”，编写Lua脚本，读取SNI名字后，直接从共享内存或者Redis里获取证书。不仅没有读盘阻塞，而且证书也是完全动态可配置的，无需修改配置文件就能够轻松支持大量的HTTPS虚拟主机。

## 小结

1. Nginx依赖于磁盘上的静态配置文件，修改后必须重启才能生效，缺乏灵活性；
1. OpenResty基于Nginx，打包了很多有用的模块和库，是一个高性能的Web开发平台；
1. OpenResty的工作语言是Lua，它小巧灵活，执行效率高，支持“代码热加载”；
1. OpenResty的核心编程范式是“同步非阻塞”，使用协程，不需要异步回调函数；
1. OpenResty也使用“阶段式处理”的工作模式，但因为在阶段里执行的都是Lua代码，所以非常灵活，配合Redis等外部数据库能够实现各种动态配置。

## 课下作业

1. 谈一下这些天你对实验环境里OpenResty的感想和认识。
1. 你觉得Nginx和OpenResty的“阶段式处理”有什么好处？对你的实际工作有没有启发？

欢迎你把自己的学习体会写在留言区，与我和其他同学一起讨论。如果你觉得有所收获，也欢迎把文章分享给你的朋友。

<img src="https://static001.geekbang.org/resource/image/c5/9f/c5b7ac40c585c800af0fe3ab98f3449f.png" alt="unpreview">


