You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 35 | OpenResty更灵活的Web服务器
在上一讲里我们看到了高性能的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正在蓬勃发展。
![unpreview](https://static001.geekbang.org/resource/image/9f/01/9f7b79c43c476890f03c2c716a20f301.png)
顺便说一下OpenResty的官方logo是一只展翅飞翔的海鸥选择海鸥是因为“鸥”与OpenResty的发音相同。另外这个logo的形状也像是左手比出的一个“OK”姿势正好也是一个“O”。
## 动态的Lua
刚才说了OpenResty里的一个关键模块是ngx\_lua它为Nginx引入了脚本语言Lua。
Lua是一个比较“小众”的语言虽然历史比较悠久但名气却没有PHP、Python、JavaScript大这主要与它的自身定位有关。
![unpreview](https://static001.geekbang.org/resource/image/4f/d5/4f24aa3f53969b71baaf7d9c7cf68fd5.png)
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虚拟机里运行。
![](https://static001.geekbang.org/resource/image/9f/c6/9fc3df52df7d6c11aa02b8013f8e9bc6.png)
下面的代码是一个简单的例子读取POST发送的body数据然后再发回客户端
```
ngx.req.read_body() -- 同步非阻塞(1)
local data = ngx.req.get_body_data()
if data then
ngx.print("body: ", 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虚拟机支持JITJust In Time技术可以把Lua代码即时编译成“本地机器码”这样就消除了脚本语言解释运行的劣势让Lua脚本跑得和原生C代码一样快。
另外LuaJIT还为Lua语言添加了一些特别的增强比如二进制位运算库bit内存优化库table还有FFIForeign Function Interface让Lua直接调用底层C函数比原生的压栈调用快很多。
## 阶段式处理
和Nginx一样OpenResty也使用“流水线”来处理HTTP请求底层的运行基础是Nginx的“阶段式处理”但它又有自己的特色。
Nginx的“流水线”是由一个个C模块组成的只能在静态文件里配置开发困难配置麻烦相对而言。而OpenResty的“流水线”则是由一个个的Lua脚本组成的不仅可以从磁盘上加载也可以从Redis、MySQL里加载而且编写、调试的过程非常方便快捷。
下面我画了一张图列出了OpenResty的阶段比起NginxOpenResty的阶段更注重对HTTP请求响应报文的加工和处理。
![](https://static001.geekbang.org/resource/image/36/df/3689312a970bae0e949b017ad45438df.png)
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依赖于磁盘上的静态配置文件修改后必须重启才能生效缺乏灵活性
2. OpenResty基于Nginx打包了很多有用的模块和库是一个高性能的Web开发平台
3. OpenResty的工作语言是Lua它小巧灵活执行效率高支持“代码热加载”
4. OpenResty的核心编程范式是“同步非阻塞”使用协程不需要异步回调函数
5. OpenResty也使用“阶段式处理”的工作模式但因为在阶段里执行的都是Lua代码所以非常灵活配合Redis等外部数据库能够实现各种动态配置。
## 课下作业
1. 谈一下这些天你对实验环境里OpenResty的感想和认识。
2. 你觉得Nginx和OpenResty的“阶段式处理”有什么好处对你的实际工作有没有启发
欢迎你把自己的学习体会写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。
![unpreview](https://static001.geekbang.org/resource/image/c5/9f/c5b7ac40c585c800af0fe3ab98f3449f.png)