今年的 OpenResty Con 在北京举行,考虑到路途过于遥远,我决定看直播。虽然参与的方式变了,但我依旧跟去年一样,趁着记忆还算清晰写了篇文章,算是“路边社”的新闻稿吧。
第一个演讲者是来自于 Strikingly 的龚凌晖,讲的是 Strikingly 内部用 OpenResty 实现的缓存系统。Strikingly 通过 OpenResty 在应用服务器前面实现了一层代理。他们把缓存数据放置在 S3/OSS 上,元数据放在 Redis 上。请求先打到 OpenResty 集群上,如果对应的缓存可用,就直接返回缓存结果,否则再访问源站并更新缓存。这个有点像放大版的 Nginx proxy_cache。凌晖的演讲有一点挺有趣的,他提到在当初做技术选型时,比较过基于 OpenResty 开发和自己造轮子两个方案。OpenResty 相关的库少,而且剩下的库还是良莠不齐的;然而自己造轮子开发量比较大,需要重新实现不少功能。相比之下,用 OpenResty 就只用造相关的库,虽然轮子还是要造的,但是工作量少了不少。
之后演讲的是来自 Kong 的 Thibault Charbonnier。Kong 公司是 OpenResty 的重度使用者,经常能看到他们的工程师分享关于 OpenResty 的演讲/文章。Thibault 本人就给 OpenResty 项目贡献了不少代码。演讲的内容是 Kong 工程师用 OpenResty 重写过去由其他软件提供的功能,使得 Kong API Gateway 这个产品变得越来越“开箱即用”的过程。过去 Kong API Gateway 除了用到 OpenResty 之外,还要打包用于生成 uuid 的 libuuid、用于进程间通讯的 serf、用于 DNS 查询的 dnsmasq,等等。这一连串玩意无论是部署还是维护起来都挺麻烦,尤其考虑到 Kong 公司做的是 To B 的产品,这种麻烦可能还要乘个系数翻几番。后面他们在 OpenResty 里面实现了 uuid 的功能(lua-resty-jit-uuid),写了 lua-resty-dns-client 这个库来进行 DNS 查询,进程间通讯的逻辑也改成在 OpenResty 内部完成。这下就把其他乱七八糟的小软件都砍掉了,仅剩 OpenResty 和数据库两种组件。作为同样做 To B 商业软件的程序员,我对这种对简化工具栈的追求深有同感。如何适配客户花样百出的现场环境,一直是做 To B 产品的一大难题。既然无法“店大欺客”地要求客户提供特定的环境,就只能遵循 less is more 的原则,尽量把软件栈做简单来避免意料之外的问题。除了讲述 Kong API Gateway 开发过程中大刀阔斧精兵简政的故事外,Thibault 还提到了他们开源出来的 worker 通讯库 lua-resty-worker-events、双层缓存库 lua-resty-ml、测试辅助库 lua-resty-busted 等等。
上午最后一个演讲是春哥介绍 OpenResty Inc. 的商业产品,也算是之前他一直唠叨的小语言的首次公开亮相吧。春哥展示了用于描述网关规则的 edgelang,语法看上去像是 erlang,能够根据模式匹配执行对应的动作。还有一个类似于 test-nginx 语法的,用于驱动测试集 testml。(有趣的是,testml 是在线上节点运行的,让我想起那个“true programmer tests in production”的段子……)此外还有个 schemalang,用于生成接口部分的代码,类似于 Swagger Api Spec。这些小语言都是用 fanlang 这门小语言开发的,最后会被翻译成 Lua 代码和 Nginx 配置。春哥还提到他们的产品力求以最小的组件获取最多的功能。跟其他 CDN 公司的技术栈不同,OpenResty Inc. 只用到了 OpenResty 和 PostgreSQL,连消息总线和内存数据库都是在 OpenResty 内部实现的。看来尽量减少组件数,确实是 To B 的公司的共识。
下午的第一个演讲,是 OpenResty Inc. 技术合伙人孙大同做的,关于新的 stream-lua-nginx-module 的介绍。为什么说是新的呢?之前 Nginx 1.9 的时候,春哥曾经基于 lua-nginx-module 改过一个 stream-lua-nginx-module。但由于 Nginx 官方在 1.13 大幅修改了 Nginx stream 子系统,原来的 stream 系统已经无法继续维护下去了。所以目前的 stream-lua-nginx-module 是基于 1.13 版的 stream 子系统重写的。大同介绍了 stream 子系统的一些功能,当然更为重要的是相关的坑。
第一个坑是 stream 系统在处理 UDP 时,是逐个包逐个包转发,性能可能会不太理想。其次由于 stream 工作在较为底层的部分,所以 LuaJIT 的 GC 对性能的影响更为明显。
另外在处理 TCP 连接过程中,由于连接的生命周期较长,频繁的内存分配可能会导致内存占用居高不下。stream 子系统的代码我没有看过,http 子系统里面,每个请求分配的内存只有在请求体生命周期结束时才会回收,不知道内存占用是否也由相似的原因导致的?
还有一个坑,stream 操作需要避免在未读完 socket 的读缓冲区前调用 close,因为 OS 会发送一个 RST 包而不会跟预期一样发送响应(对该现象的详细解释)。这个问题在 http 子系统中没有暴露出来,但是如果你需要跟更低层级的协议打交道 -- 你需要看下他们为此新增的 shutdown 接口。
大同还提到 stream 子系统中的 ngx.print/say 和直接操作 ngx.req.socket 会有冲突,需要调用 ngx.flush 及时输出响应。
最后有一个重要的点,stream-lua-nginx-module 是由 meta-lua-nginx-module 里面的 tt2 模板文件编译出来的。用模板来生成代码有利于同时保证 http 版本和 stream 版本能够及时同步,不过如果有修改,那么需要先改 C 代码,测试通过后再反哺回模板文件,貌似会比较麻烦?
紧跟在大同之后的演讲,是 codedump 老师关于 Lua 5.1 GC 实现的介绍。LuaJIT 虽然跟 Lua 5.1 有天壤之别,但是 GC 这一块却是大同小异的。codedump 老师讲解了 Lua 之中的增量标记-清扫回收算法,里面用的是经典的三色标记(未标记对象为白、正在标记对象为灰、子对象全部标记完毕的对象为黑,垃圾回收时回收白色对象)。美中不足的是,可能由于时间关系,细节部分没有更好地说明,比如为什么 table 的屏障和其他对象不同等等。如果对这些细节感兴趣,可以看下 codedump 老师自己写的一篇文章:Lua GC。随便吐嘈下 LuaJIT 计划的新一代GC 什么时候能够实现呢?现行的 GC 实现跟其他高性能语言差距挺大的呢。也许新的 LuaJIT 维护者会带来一些改变。随便解答下 Q&A 阶段那个标记-清扫算法如何解决循环依赖的问题:如果循环依赖的多个对象中,每一个对象都没有被其他对象引用,则初始化阶段后这几个对象一直都是白色,于是就能被回收了。
下一个演讲者是李凯,快乐茄的后台研发工程师。因为他的演讲偏向于具体的业务,所以我也没什么记录的…… 印象里他问了个为什么用 ngx.timer.at(0, ...)
会比 ngx.thread.spawn
更占用资源的问题,然后孙大同回答,说是前者(timer 对象)会创建一个包含 Lua Thread 的 fake request 对象,而后者只创建 Lua Thread 相关的对象,所以会更加轻量。
当天收官的演讲者是来自于新浪微博的周晶,OpenResty 社区的老朋友。周晶讲的是当 Motan RPC 框架遇见 OpenResty,两者碰撞出的火花。可惜我对 RPC 框架没什么了解,所以听得云里雾里的。不过周晶提到一个有趣的思路,就是像 PHP 这一类的语言只实现 RPC 的 client,server 部分可以由 OpenResty 负责协议的卸载,然后再以 CGI 的方式转调背后的实现。如果有用 OpenResty 介入 RPC 的需求的同行,建议听下这个演讲。(IT大咖说上有视频回放)