OpenResty 的过去、现在和未来(上)

2020-02-24 11:49:19 浏览数 (1)

题图是 OpenResty 大会第二天的小规模圆桌会议。

这是春哥在去年 OpenResty 大会上的分享实录。满满的干货,我搭配 PPT 整理为三部分,分期发出来给大家。如果是有什么错漏,是我整理的问题。


大家好,我叫章亦春,我喜欢用 agentzh 这个名字,我很讨厌别人把我这个首字母 a 大写,我觉得特别丑,所以大家千万不要大写,这是雷区。

今天主要想和大家分享下 OpenResty 的过去,现在和未来。如果我在其他的 IT 交流会议上面重点介绍我做了什么,或者正在做什么,那么在 OpenResty 这个我们自己的会上面,我就可以放心大胆的说我们将要做什么。

其实最初我只是想自己搭一个个人博客,我不想用 wordpress,也不想用其他现成的技术,我想自己用一个轻量级的技术做出来。所以最初只是一个非常单纯的想法。

我的第一份工作是在 Yahoo 中国的搜索部门,我当时有很多很碎的业务,比如搜索结果的相关推荐,或者垂直搜索,还有下拉列表的自动提示,很多这种很碎的功能在搜索部门。于是我们老大让我写一个通用的平台,可以方便的构造这样的接口,给前前端来展现。这些接口可能是吐 json 这种很简单的 API。那个年代,API 的概念还是方兴未艾,顺应这个潮流,做了第一版的 OpenResty ,是由我当时最喜欢的脚本语言 Perl 写的。花了很大的精力去优化,因为当时在 Yahoo 使用的生成机器是爬虫淘汰下来的机器,就是连爬虫都觉得很慢的机器,拿给我们跑业务跑生产,所以在用 Perl 做这种访问量很大的接口时候,很难进行优化。当然我也不断去把越来越多的 OpenResty 功能用 C 去实现,但是还是很难达到让人眼前一亮的性能要求。所以现实是非常残酷的。

我在 Yahoo 有一个同事,叫 chaoslawful,大名叫王晓哲,我很有幸拜他为师,学了很多系统编程方面的东西,因为他是一个很了不起的人,也是一个自学成才的工程师。

在 09 年的时候,就一直有一个想法,把之前用 Perl 的 OpenResty 进行重写,然后达到一个很高的性能要求。在 09 年的时候,我和晓哲转到了淘宝,当时加入了量子统计这个数据平台部门,做量子统计这个产品,给卖家提供流量统计、销售统计、广告效果报表的数据分析产品。现在这个产品已经不存在了,是很多年前淘宝的一个产品。它有很复杂的业务逻辑,对性能也有比较高的要求,同时后台的数据量是很恐怖的,淘宝大部分商家的数据都会在这里展现,进行各种维度和搜索的分析。

当时,我就想,我既要把 OpenResty 重写了,得到很高效的框架,同时也要和部门的同事一起,把量子统计在这个平台上面构建出来。所以很难得有这个机会,同时也是非常大的一个挑战。我希望在这里尝试一些从未尝试过的、新奇的玩法。所以大家现在看到的 OpenResty ,其实就是在那段时间完成的,09 年到 2011 年,我在淘宝的时间中。同时把量子统计这个 web 应用进行了非常高效的实现,相比之前 PHP 实现的老版本,代码量减少了90%,性能提升了一个数量级,包括延时,包括并发。我们后台的一个数据库工程师和我说,之前老板点击一个黄钻卖家的报表,离开工位倒杯水回来,报表还在转,新版的话,啪一下就出来了。所以效果还是非常明显的。只用非常少的机器,就能处理非常复杂的数据库相关的查询,因为它数据源很多,所以这里面有复杂的关系型的融合还有分表的逻辑。

那么,大家可能会觉得, Lua 作为一个非常简单的语言,来表达很复杂的业务系统,可能会很吃力,事实上,我们在构建这个业务系统的时候,并没有写多少 Lua 代码,但确实是由 Lua 来驱动的。技巧就是我经常鼓吹的一种玩法:设计一种针对业务的小语言,或者说DSL。

这个应用背后的数据平台,就是用我自己设计的一种类似关系型的语言来表达的,然后我自己实现的编译器,处理这种业务描述,生成高度优化的 Lua 代码。 Lua 代码生成之后,你会觉得人类是很难写出来的,因为有很多优化,是人很难做对的。对查询的拆分,对查询的优化,所以我又在里面实现了一个中间件,但代码很少,所以我等于把 Lua 语言当做虚拟机的机器语言在使用,业务我是用最适合业务模型的一种表达方式来表达的。这块儿我可以讲很多,可以讲一天。这块儿和 OpenResty 配合起来去使用的一个优势就在于,这个平台框架很轻,没有什么乱七八糟的东西,同时 Lua 语言是一种高度动态的语言,我可以根据需要去做一些魔法般的事情,把它做成我的虚拟机的 CPU,来进行代码生成会非常的方便,同时效率也会非常非常的高。后面我还会讲到类似的例子。

我觉得很多时候不用纠结到底使用什么编程语言,更多的应该考虑用业务语言来表达我们的业务。比如团队里面产品经理用犀利的语言把业务描述的很精确,那么他写的这个文档, 已经是这个业务系统本身了。我们写个编译机让它跑起来就行了。大家可以去想一想这个思路。

在 2012 年的时候,我加入了 Cloudflare。2011年的时候,我在福州,我不是福州人,很多人认为我是福州人,回老家,其实不是,我们只是随便挑选了一个南方城市,去过一种半隐居的生活,有更多的时间专注于开源项目,特别是 OpenResty 。现在大家看到的很多高级功能,都是在那段时间完成的。在 2012 年,福州田园生活过了一年之后,就加入了美国的 Cloudflare 公司,这是一个 CDN 公司。所以我之前在搜索行业、在数据分析行业混了几年,然后到了 CDN 行业。机缘巧合,这家公司希望用 OpenResty 来构建他们的基础设置。我觉得去美国过类似的田园生活也不错。但我没想到的是 OpenResty 在 CDN 行业获取了很大的成功,有很多的公司会去使用。这也是我最初没有想到的,我最初想的是 web 应用,对富客户端或者移动客户端的 API,使用 OpenResty 构建是非常合适的。大家之前看到的 web 引用,其实也是由这些 API 驱动的,大量使用了 AJAX 这样的技术,像模板渲染、用户交互的控制流逻辑实现都是在浏览器里面完成的,这是一个胖客户端,当时花了很多精力去搞 IE6,所以又写了很多 javascript,还是很辛苦的。

最开始我们考虑把 Lua 嵌入到 NGINX 当中,也是在淘宝的时候,晓哲老师给我建议了这样一个技术方案,我觉得靠谱。我最早的改写方案是基于 Apache 去写 c module,但是 Apache 的代码看得我有些云里雾里,所以在晓哲老师的建议下,开始把工作重心放在 NGINX 上面。这时候可能知道 NGINX 的同学也不是很多。 NGINX 的请求有多个不同的处理阶段,在不同的阶段,我们可以插入一些 Lua 代码,插入自己的逻辑,进行一些控制或者事件记录。当然最常用的就是 content_by_lua ,这个是用 OpenResty 做完整外围应用的一种玩法,刚才很多同学已经演示了,可能对于 CDN,对于动态均衡器来说,他就不会用content_by_lua ,而是用 NGINX 自己的 proxy_pass 这样的反向代理模块,而且会去使用 access_by_lua 和 rewrite_by_lua 这样一些在更早阶段执行的 hook去加入自己的一些逻辑。

我开始的时候写了一些 resty 的 Lua 库,可以在 Lua 层面做一些代码的复用。然后社区的热心用户贡献了很多 resty 库,特别是今天上午演讲的 Aapo 同学,贡献了非常多的 resty 库,这些都是让我很振奋的事情。在做这些库的时候,我有一个愿望,就是他们都是相对独立的,尽量之间不要有相互依赖,这样可以确保一个清晰的结果, Lua 的优点就在于他的简洁明快。

刚才提到三个应用场景,API Server,还有HTTP Proxy,这个在 CDN 行业用的比较多。Web Application,这个相对少一些。这个和 NGINX 本身的使用方式有关。有些同学可能没法接受把 NGINX 本身作为Web Application的容器或者server,但这是我的初衷。

在发展的过程中, OpenResty 秉持的是兼容并包的思想,我们并不排他。出于 NGINX 在整个web stack中位置的特殊性,我们可以很方便的和现有的技术进行融合,比如PHP、Python、go、nodejs,我们在网关这个层面,所以我们可以同时和其他后端应用并存,虽然我还是更倾向于更纯净的方案,但事实上,在 OpenResty 社区里面,我们的用户来自各个社区,Ruby、Python甚至java,所以我很高兴看到不同语言社区的同学,把他们自己社区的文化,一些看待问题考虑问题的方法,能够带到我们社区里面来,扩展我们的思路。我们也鼓励各种混合的使用方法,至少对于现存的系统来说,我们的迁移可以一块一块的进行,当然用户认证的这个逻辑需要优先迁移到 NGINX 这个层面。这种玩法也是我最初没有想到的。我最初用 NGINX ,是看中它http、io事件处理模型这块的实现,大家最最最为经典的用法是把 NGINX 最为前面的网关,后面用 fcgi 去连API server,或者 proxy_pass 去做反向代理。那么在这种结构下, OpenResty 更有机会去做更多的事情,融合现有更多的业务。

我自己也用过这种技巧,在量子的时候,有一个实时统计的引擎,有很复杂的线路协议,当时我在做业务迁移的时候,因为很严格的上线的时间点,所以没有时间去完成一个非阻塞性能很高的实时引擎客户端,所以我简单的用 Perl 实现了一个客户端,虽然我很仔细的实现了,但其实不行,扛不住,成为整个系统的瓶颈。在上线之后,有了更多时间之后,我写了一个纯c的 NGINX module,来进行非阻塞通信。刚才我讲用 Perl ,用c,其实并不是我直接用 Perl ,用c去实现,而是用了一个写程序的程序的技术

这个实时数据库的维护者,也是我的同事,他维护了一篇非常漂亮的文档,用来详细讲解线路协议的每一个主要方面。我一看到这个文档,我灵机一动,这个文档就是我的客户端的实现。我写了一个 Perl 脚本去自动分析这个文档,把里面的数据,里面的结构,全部抽出来,变成一个数据结构,自动生成 Perl 实现,自动生成c的实现。事实上,我还让它自动生成了测试集,测试也可以自动生成。在这个过程中,我发现他文档中的一些笔误,因为毕竟是人写的,而我的分析器尝试把它当做程序来运行的时候,就会发现很多细节问题。所以文档真的很重要,重要到你可以用文档来生成任何东西,包括实现,包括测试,中文的,英文的,葡萄牙文的各种人类的文档都可以。

这是一种很有意思的思路。这也使得你的业务和具体实现无关,在任何一天,你都可以换掉下面的实现,而不用动上面的实现代码。像刚才我举的那个例子,我可以很惬意的把 Perl 实现换成 C 实现,其实我只是写了一个模板。前端工程师可能很熟悉这种模板的技术,我们一般会使用模板来生成 HTML 和 CSS,但是你有没有想过,我们可以用模板生成任何东西,包括你的程序本身,为什么不呢?所以我使用写程序的程序的技术的时候,我恰恰是用了那种通常是生成 HTML 的模板引擎,当然更复杂的代码生成器,还是需要专门的代码生成器技术。这个说远了,我的意思是说,这种兼容并包的哲学,可以打开我们的思路,我们不必局限于 Lua 这个语言, NGINX 这个东西,其实我们只是根据需要,把很多我们需要的块拼接起来,而并没有任何的排他或者任何宗教信仰式的极端主义的倾向。

未完待续,下期更精彩

0 人点赞