01 初探OpenResty的三大特性

你好,我是温铭。

开篇词里我们说过,OpenResty的优势显而易见。不过,在具体学习之前,让我们先简单回顾下 OpenResty 的发展过程,这有助于你对后面内容有更好的理解。

OpenResty的发展

OpenResty 并不像其他的开发语言一样从零开始搭建,而是基于成熟的开源组件——NGINX 和 LuaJIT。OpenResty 诞生于 2007 年,不过,它的第一个版本并没有选择 Lua,而是用了 Perl,这跟作者章亦春的技术偏好有很大关系。

但 Perl 的性能远远不能达到要求,于是,在第二个版本中,Perl 就被 Lua 给替换了。 不过,在 OpenResty 官方的项目中,Perl 依然占据着重要的角色,OpenResty 工程化方面都是用 Perl 来构建,比如测试框架、Linter、CLI 等,后面我们也会逐步介绍。

后来,章亦春离开了淘宝,加入了美国的 CDN 公司 Cloudflare。因为 OpenResty 高性能和动态的优势很适合 CDN 的业务需求,很快, OpenResty 就成为 CDN 的技术标准。 通过丰富的 lua-resty 库,OpenResty 开始逐渐摆脱 NGINX 的影子,形成自己的生态体系,在 API 网关、软WAF 等领域被广泛使用。

其实,我经常说,OpenResty 是一个被广泛使用的技术,但它并不能算得上是热门技术,这听上去有点矛盾,到底什么意思呢?

说它应用广,是因为 OpenResty 现在是全球排名第五的 Web 服务器。我们经常用到的 12306 的余票查询功能,或者是京东的商品详情页,这些高流量的背后,其实都是 OpenResty 在默默地提供服务。

说它并不热门,那是因为使用 OpenResty 来构建业务系统的比例并不高。使用者大都用OpenResty来处理入口流量,并没有深入到业务里面去,自然,对于 OpenResty 的使用也是浅尝辄止,满足当前的需求就可以了。这当然也与 OpenResty 没有像 Java、Python 那样有成熟的 Web 框架和生态有关。

说了这么多,接下来,我重点来介绍下,OpenResty 这个开源项目值得称道和学习的几个地方。

OpenResty的三大特性

详尽的文档和测试用例

没错,文档和测试是判断开源项目是否靠谱的关键指标,甚至是排在代码质量和性能之前的。

OpenResty 的文档非常详细,作者把每一个需要注意的点都写在了文档中。绝大部分时候,我们只需要仔细查看文档,就能解决遇到的问题,而不用谷歌搜索或者是跟踪到源码中。为了方便起见,OpenResty 还自带了一个命令行工具restydoc,专门用来帮助你通过 shell 查看文档,避免编码过程被打断。

不过,文档中只会有一两个通用的代码片段,并没有完整和复杂的示例,到哪里可以找到这样的例子呢?

对于 OpenResty 来说,自然是/t目录,它里面就是所有的测试案例。每一个测试案例都包含完整的 NGINX 配置和 Lua 代码,以及测试的输入数据和预期的输出数据。不过,OpenResty 使用的测试框架,与其他断言风格的测试框架完全不同,后面我会用专门章节来做介绍。

同步非阻塞

协程,是很多脚本语言为了提升性能,在近几年新增的特性。但它们实现得并不完美,有些是语法糖,有些还需要显式的关键字声明。

OpenResty 则没有历史包袱,在诞生之初就支持了协程,并基于此实现了同步非阻塞的编程模式。这一点是很重要的,毕竟,程序员也是人,代码应该更符合人的思维习惯。显式的回调和异步关键字会打断思路,也给调试带来了困难。

这里我解释一下,什么是同步非阻塞。先说同步,这个很简单,就是按照代码来顺序执行。比如下面这段伪码:

local res, err  = query-mysql(sql)
local value, err = query-redis(key)

在同一请求连接中,如果要等 MySQL 的查询结果返回后,才能继续去查询 Redis,那就是同步;如果不用等 MySQL 的返回,就能继续往下走,去查询 Redis,那就是异步。对于 OpenResty 来说,绝大部分都是同步操作,只有 ngx.timer 这种后台定时器相关的 API,才是异步操作。

再来说说非阻塞,这是一个很容易和“异步”混淆的概念。这里我们说的“阻塞”,特指阻塞操作系统线程。我们继续看上面的例子,假设查询 MySQL 需要1s 的时间,如果在这1s 内,操作系统的资源(CPU)是空闲着并傻傻地等待返回,那就是阻塞;如果 CPU 趁机去处理其他连接的请求,那就是非阻塞。非阻塞也是 C10K、C100K 这些高并发能够实现的关键。

同步非阻塞这个概念很重要,建议你仔细琢磨一下。我认为,这一概念最好不要通过类比来理解,因为不恰当的类比,很可能把你搞得更糊涂。

在 OpenResty 中,上面的伪码就可以直接实现同步非阻塞,而不用任何显式的关键字。这里也再次体现了,让开发者用起来更简单,是 OpenResty 的理念之一。

动态

OpenResty 有一个非常大的优势,并且还没有被充分挖掘,就是它的动态

传统的 Web 服务器,比如 NGINX,如果发生任何的变动,都需要你去修改磁盘上的配置文件,然后重新加载才能生效,这也是因为它们并没有提供 API,来控制运行时的行为。所以,在需要频繁变动的微服务领域,NGINX 虽然有多次尝试,但毫无建树。而异军突起的 Envoy, 正是凭着 xDS 这种动态控制的 API,大有对 NGINX 造成降维攻击的威胁。

和 NGINX 、 Envoy 不同的是,OpenResty 是由脚本语言 Lua 来控制逻辑的,而动态,便是 Lua 天生的优势。通过 OpenResty 中 lua-nginx-module 模块中提供的 Lua API,我们可以动态地控制路由、上游、SSL 证书、请求、响应等。甚至更进一步,你可以在不重启 OpenResty 的前提下,修改业务的处理逻辑,并不局限于 OpenResty 提供的 Lua API。

这里有一个很合适的类比,可以帮你理解上面关于动态的说明。你可以把 Web 服务器当做是一个正在高速公路上飞驰的汽车,NGINX 需要停车才能更换轮胎,更换车漆颜色;Envoy 可以一边跑一边换轮胎和颜色;而 OpenResty 除了具备前者能力外,还可以在不停车的情况下,直接把汽车从 SUV 变成跑车。

显然,掌握这种“逆天”的能力后,OpenResty 的能力圈和想象力就扩展到了其他领域,比如 Serverless 和边缘计算等。

你学习的重点在哪里?

讲了这么多OpenResty的重点特性,你又该怎么学呢?我认为,学习需要抓重点,围绕主线来展开,而不是眉毛胡子一把抓,这样,你才能构建出脉络清晰的知识体系。

要知道,不管多么全面的课程,都不可能覆盖所有问题,更不能直接帮你解决线上的每个 bug 和异常。

回到OpenResty的学习,在我看来,想要学好 OpenResty,你必须理解下面8个重点:

这些内容正是我们学习的重点,在专栏的各个模块中我都会分别讲到。在学习的过程中,我希望你能举一反三,并且根据自己的兴趣点和背景,有针对性地深入阅读某些章节。

如果你是 OpenResty 的初学者,那么你可以完全跟着专栏的进度,在自己的环境中安装 OpenResty,运行并修改示例代码。要记住,你的重点在于构建 OpenResty 的全貌,而非死磕某个知识点。当然,如果你有疑问的地方,随时可以在留言区提出,我会解答你的困惑。

如果你正在项目中使用 OpenResty,那就太棒了,相信你在阅读 LuaJIT 和性能优化章节时,一定会有更多的共鸣,更能应用到实际,在你的项目中看到优化前后的性能指标变化。

另外,如果你想要给 OpenResty 以及周边库贡献代码,那么最大的门槛,并不是对 OpenResty 原理的理解,或者是如何编写 NGINX C 模块的问题,而是测试案例和代码规范。我见过太多 OpenResty 的代码贡献者(也包括我自己),在一个 PR 上反复修改测试案例和代码风格,这其中有太多鲜为人知的潜规则。所以,专栏的代码规范和单元测试部分,就是为你准备的。

而如果你是测试工程师,即使你不使用 OpenResty,OpenResty 的测试框架和性能分析工具集,也必能给你非常多的启发。毕竟,OpenResty 在测试上面的投入和积累是相当深厚的。

写在最后

欢迎你留言和我分享你的 OpenResty 学习之路,在这期间,你又走过哪些弯路呢?也欢迎你把这篇文章转发给你的同事、朋友。

还是那句话,在学习的过程中,你有任何疑问,都可以在专栏中留言,我会第一时间给你答复。