题图是 OpenResty 大会第二天的小规模圆桌会议。
这是春哥在去年 OpenResty 大会上的分享实录。满满的干货,我搭配 PPT 整理为三部分,分期发出来给大家。如果是有什么错漏,是我整理的问题。
大家注意我微博的话,可以看到我很多时间花在 Streaming Regex 这个正则引擎上面,这是一个从零实现的正则引擎。
web 世界很难离开正则,因为总会遇到各种文本处理,对于 CDN 来说尤为如此,因为我们需要对请求进行很复杂的模式匹配,不管是 WAF,还是 CDN 的规则的调度分发,还包括对响应体的一些正则替换,去掉或者删除一些东西,比如把电子邮件地址替换成它对应的图片,这些都需要很强大的正则引擎,支持流式处理,可以分块来进行匹配,这样的引擎是永远不会往后回溯的,我处理一块扔掉一块,再处理下一块,用 O(1) 的内存来处理任意长度的数据流。
这个libsregex还在开发当中,我做了一些尝试和实践,最新的是基于确定性有穷自动机(DFA)来实现的。我觉得这里面有一些算法的创新,我也跟 PCRE JIT 的作者有过沟通,他也很关注这个项目,很早就主动和我联系,让我有些受宠若惊。所以我很高兴通过这个项目,吸引了一些正则这个领域举足轻重的人物的注意和帮助。他们会问需不需要帮助啊,或者说你的这个算法好有意思啊,你教教我好不好,我们一起讨论一下。这些互动是非常非常宝贵的,这也会打破公司的边界甚至国界,这是开源带给我们的好处和乐趣。
我还写了一个 NGINX 的模块,用正则在响应体里面做替换,它现在效率不太好,但是它实现了这个想法,它现在是确定性有穷自动机的算法,所以比较慢,和 RE2 的一般情况一样慢,我需要像 PCRE JIT 一样性能级别的引擎。最新的 libsregex 里面的 DFA 引擎,在平均情况下可以达到 PCRE JIT 甚至更高的效率,最坏的情况下,也能和 RE2 或者比 RE2 更好。Intel 在今年10月份开源了一个做了7年的正则库,叫 HyperScan,也是值得去参考的,只不过它的语义和 Perl ,和PCRE的差别更大一些。但它的优点是至少可以处理上万个正则的流式匹配。
另外 lua-resty-pegex 也是我想做的一个库,我其实并不想把社区可以做的事情都做完,我只是捡一些我特别想做的库。一旦有了iresty之后,标准库和非标准库的边界已经不是那么清晰了。pegex 是 Perl 里面我非常喜欢的用于语法分析的引擎,可以用于构造编译器,小语言,写程序的程序,我们需要这种分析机构造器去实现小语言的编译器,让计算机认识我们定义的语法,认识我们某个同事写文档的格式或者惯用语,那么让计算机懂得这个文法,我们就需要另一种小语言,来描述这种小语言的语法,那么这些工具就可以把描述变成实现,让计算机能够理解我们想象的语言的语法。语义分析可能会更复杂一些,因为没有这种自动化工具,但实现起来也没有那么复杂。
编译原理应该是我计算机专业学习当中,让我受益最大的一门课。在这里领域的很多想法,不仅适用于我们常见的 gcc、java 这样的全功能的工业级别的编译器,也适用于小巧的,只有几百行、几千行自己实现的小语言的编译器。
基于这种小语言的思路,我希望 OpenResty 可以提供更多的小语言,让大家可以去接受、去摆弄这样的一些想法。我脑海里面的 Edge 语言就是这样一个例子,它是为 CDN 行业里面典型的业务模型、业务逻辑来设计的,可以很方便的通过基于规则的语法来表示很复杂的过滤规则、分发规则、WAF的防火墙规则,用一种统一的方式来表达我们的意图,而不是去表达一种实现。那么我们的编译器就有机会在更高的层面上进行业务的语义分析,来写很了不起的全局优化。比如说,我们有100个规则,都需要对 URI 进行正则检查,我们自己实现的优化器,可以把规则里面对 URI 的模式,合并成一个自动机,这样我们只用对 URI 扫描一遍,而不是100遍。所以这样就打开了我们优化的可能性。
我很想看到的一件事情是 OpenResty 有一个官方的WAF平台,现在开源的一些解决方案,至少我觉得可以做的比它更好。包括 cloudflare 正在线上使用的 WAF,我觉得也可以做的更好。我希望最好的 WAF 应该是开源的,而且是 OpenResty 里面的。对于 WAF 来说,是一个很经典的 DSL 的例子,我们使用一种小语言,比如刚才的 Edge 语言,也可以扩展到 WAF 场景,动作可能是reject、pass、打分,分值达到某个程度之后reject,返回一个403,完成一个 capture 来进行验证。
我觉得特别有趣的一种玩法是从 DSL 生成高度优化的 Lua 代码。这样就可以发挥包括 Lua JIT在内的组件的威力,对于JIT编译器来讲, Lua 代码分支越少,越简单,越有可能生成最高效的机器指令,同时我们因为使用了编译的方法,我们抽象本身的开销可以在编译期,在上线之前完成。我们最后上线的时候,是生成的非常小巧的 Lua 代码,没有什么运行时开销,也没有什么依赖项。魔法都可以在编译期完成,这是一种脱去抽象本身开销的很好的思路。现在很多web框架,是在运行时进行抽象,那么会导致在线时候,内存和 CPU 的损耗很大,因为你引入了很多层次,这些开销是运行时的开销。这一点也可能不是上线之前,也可能是在一些特殊的配置端口,比如说 CDN 的配置界面上面,我们会做编译,通过一些复制的方式,比如KT,推送到全球各个网络的各个节点,我们可以推送 Lua 字节码。在 CDN 的场景下,等价于于我们为每一个客户,高度定制了一个 CDN 软件。这样如果你的 CDN 配置很简单,那么生成的 CDN 软件就非常简单。
我们社区常常会吸引到其他社区,包括 redis 社区的邮件,比如 redis 社区的一个哥们儿就发邮件和我讨论 stateful 这个概念,这个我也是从他那里听说的。所以有一些很有趣的协议和方法,当然每一种协议都有自己的折中和自己的应用场景。我希望后面 OpenResty 能在这方面多做一些尝试,各个 OpenResty 之间形成更加默契配合的关系。同时能给请求引入状态,这个状态可以在同一机房的不同节点传递,甚至是跨机房,在全球节点之间传递。这些都是可以玩的一些东西,也会有它们各自的应用场景。
semaphore 是一个很重要的特性,它可以用于 ngx_lua 轻量级线程之间的同步,其实就是线程同步里面的信号量。酷狗音乐的同学一直在开发这个功能,已经有一些很不错的成品了。我还在 review 过程当中,希望能尽快融合进主干,这个可以避免使用 sleep 的方法进行同步,损耗可以下降非常多。这个朱德江同学的分享里面也会提到。
酷狗音乐的朱德江同学还实现了基于list的共享内存的类型支持,可以用redis风格的接口对共享内存进行队列操作,这个也会有非常有趣的应用场景。
我自己也很想拿Shm-based databases来练手,在 OpenResty 里面来实现一个内存数据库,没有持久化,或者有限持久化支持的数据库。关系类型的,或者其他类型的,时间序列类型的。
init_by_lua 一直不支持cosockets,我希望这个能增加支持。在 NGINX 启动的时候,在外部的tcp或者udp服务里面拿一些数据,这个还是非常有用的。那目前需要在每个worker里面做一些比较恶心的初始化同步工作。
我们需要有更好的cosockets。cosockets仍然像经典的socket同步的使用,但它是非阻塞的,不会阻塞任何操作系统线程。我们有一些内存是分配在 NGINX 的内存池里面,我希望可以避免这种分配,更适用于推送的场景,很多下游长连接保持非常长时间的场景。
UDP或者gram socket需要一个bind()方法,这样我们可以进行双向的服务发现,服务推送之类的东西。
cosockets连接池,我希望能够基于它做一些后端的并发控制,当超过这个连接池容量的时候,可以对connect请求进行排队,而确保后端不会因为并发数太多而损失吞吐量,即使是redis,如果给他并发太高的话,吞吐量也会直线下降。
ngx.connection 这是一个比较新的东西,和cosockets平行。我希望能够提供一个fd,也就是文件描述符,这个fd可能是其他c库实现的,我们可以把fd注册到 NGINX 的事件循环里面,然后可以去同步非阻塞的等待 NGINX 事件循环里面的事件,比如说read、write。这种模式的好处在于我们可以结合 Lua jit的ffi,很方便的整合现有的第三方的支持非阻塞IO的c库,比如postgres的libpq,而不用 Lua 的cosockets去重写这样的库。这个也是能够改变 OpenResty 生态系统的一个特性。幸运的是,这个特性实现起来很简单。而且github上面已经有了一个pull request,叫做wait for fd,有兴趣的同学可以一起参与讨论,一起研究。
balancer_by_lua 这个刚刚开源,你可以用 Lua 来定义自己的负载均衡器,可以在每个请求的级别上去定义,当前访问的后端的节点地址、端口,还可以定制很细力度的访问失败之后的重试策略。如果我是重试还是不重试,重试失败后去重试哪个节点,都可以在每个请求的力度上进行 Lua 编程。这对于很多反向代理风格的业务来讲,是一个很重要的特性。
SSL现在也很热门,大家为了安全起见,都往HTTPS迁移。 ssl_certificate_by_lua 可以让你通过lua严格去控制下游的ssl握手的过程。比如它当前使用什么ssl,使用什么证书,是不是让它握手,都可以在这个环节插入自己的lua 代码来完成。比如cloudflare的ssl网关就是利用这个特性来实现的。
我们可以通过 KT 这种 key value store 来分发我们客户的私钥和证书,到我们全球各个网络的节点上面。这是一个按需加载的过程,我们不可能在每台机器上面加载所有客户的证书,因为非常多。而且也没有必要,因为流量的 ? 是非常明显的。我们只需要按需加载,然后缓存就是非常有效的。
我的同事,也是一个中国人,他照猫画虎的实现了 ssl session 的一些 hook,这样我们可以用 Lua 来控制 ssl session id 的缓存策略,比如我们把 ssl session id 在跨机房或者同机房跨机器之间进行共享,这样可以减少 ssl 握手的次数,因为 session id 是需要服务器端保存状态的。那么新的 TLS 扩展基于 session ticket,那就由客户端负责来维持这个状态,就不需要服务端做这种事情。但为了兼容老的 https 客户端,还是有必要做这方面的工作。
fetch_by_lua 是支持非阻塞 IO,你可以在里面使用 cosockets,可以去连 memcached,或者支持 memcached、redis 任意的这种外部的协议。然后 stored_by_lua 是不支持 IO 的,其实也没必要,它支持 timer,可以建一个 timer 异步的去把当前生成的 session 存到全局的 cache 里面。这个也是正在 review ,即将开源的一个特性,它需要 openssl 的支持。
ssl session ticket key ,就是刚才提到的客户端保存状态的东西,它需要一个 key 来进来加密,为了安全起见,这个 key 需要定期轮转。这方面我们也做了一些工作,后面也可以办法把它开源。 我记得 twitter 也开源了一个东西,但我们的血统更纯正,直接在 NGINX 里面做,没必要再去挂一个服务。
LuaJIT 我们也有很多工作,我们需要一个更好的 LuaJIT GC;然后 string.buffer ,我们需要引入一种可以自我销毁、自我修改的字符串类型。
还有一个更疯狂的想法,就是基于 LuaJIT,可以有 Perl、Python、Ruby、PHP 这些常见编程语言的方言,某个核心子集,它们运行在同一个 VM 上面,这意味着 Lua 里面所有非阻塞的库都可以在这些语言的方言里面调用。缺点就是这些语言的 C 的扩展我们是不可能支持的,因为我们的 VM 和这些主流的 VM 是不一样的。
对于调试来说,基于 gdb 我们做了很多工作。 现在我们可以在 gdb 里面设置指定 Lua 函数的入口和出口位置的断点,当然有个限制是你的 Lua 代码必须是 LuaJIT 解释执行的。你可以设置断点之后 continue,然后触发断点,你可以检查 Lua 函数的调用栈,Lua 某个全局变量的值,Lua 某个上下文变量的值。大家有兴趣可以玩一下,这个工具是用 Python 写的,我是为了写这个工具才学的 Python,因为 gdb 只支持 Python 来进行扩展。还有很多基于 gdb 的对 LuaJIT GC 进行分析的工具,比如说你的代码有内存泄露的时候,这样的工具会非常有用。当然 Lua 调用栈的分析也是现成的代码。
systemtap 也是我们投资很多的一个东西,它是 Linux only。我把它扩展了一下,引入了一个高级的宏语言,我很喜欢自己发明语言。通过它可以构建内核追踪的工具,非常适合在线分析。刚才提到的 gdb 适合离线分析。systemtap 适合在线分析,对生成系统的影响是非常有限的。这个工具是 redhat 开发的,较新的 Linux 已经提供它所需要的所有特性。我有两个项目:stap 和 nginx-systemtap-toolkit,里面有很多针对 NGINX 和 LuaJIT 的工具,都是我们在常年的生成、运维、开发过程当中积累下来的,大家有兴趣可以看看,都在 Github 上面。
lldb (LLVM 下的调试器)我们也可以做一些工具,但现在还没有;eBPF,基于LInux的一个新的基于内核的 VM,我们可以在上面做一些有趣的事情;那么 Y 语言,是我设计的一种小语言,很像 C 语言,我们用它来写工具, 这样可以通过编译器来生成各个不同测试平台的工具实现,这样就没必要针对不同平台重新写一遍工具,这样很累,非常累,因为我之前经常做这种事情。我往往会针对 systemtap 写套工具,针对 gdb 写套工具,那么我现在只用写一套就行了,而且它直接就是 C 语言,至少是极其像 C 语言的某种语言来实现的;Mozilla rr 也是很有趣的工具,大家可以去看看,这里没有时间讲了。
Test:Nginx 我一直在搞的测试框架,也是 DSL,我块儿我打算写一个教程,放在书里作为一个章节详细介绍下。这个是积累多年的测试框架,不仅适用于 NGINX C 模块或者核心的开发,也适用于 OpenResty Lua 库甚至是 Lua 业务系统的回归测试。Cloudflare 的业务系统现在基本上就是用这种方式来做回归测试的,非常优美,因为测试一定要优美。