笔者在今年的 COSCUP 大会做分享时,曾有观众问这样的问题,为什么 APISIX、Kong 和 3scale 这些网关都采用 Lua 来编写逻辑?
是啊,Lua 并不是一门广为人知的语言,离“主流编程语言”的圈子大概还差个十万八千里吧。甚至有一次,我在跟别人交流的时候,对方在说到 Lua 之前,先停顿了片刻,随后终于打定主意,以 “L” “U” “A” 逐个字母发音的方式表达了对这一罕见之物的称呼。
所以,为什么 APISIX 和其他知名的网关会选择用 Lua 呢?
事实上,APISIX 采用的技术栈并不是纯粹的 Lua,准确来说,应该是 Nginx + Lua。APISIX 以底下的 Nginx 为根基,以上层的 Lua 代码为枝叶。
LuaJIT VS Go
严谨认真的读者必然会指出,APISIX 并非基于 Nginx + Lua 的技术栈,而是 Nginx + LuaJIT (又称 OpenResty,以下为了避免混乱,会仅仅采用 Nginx + Lua 这样的称呼)。
LuaJIT 是 Lua 的一个 JIT 实现,性能比 Lua 好很多,而且额外添加了 FFI 的功能,能方便高效地调用 C 代码。
由于现行的主流 API 网关,如果不是基于 OpenResty 实现,就是使用 Go 编写,所以时不时会看到各种 Go 和 Lua 谁的性能更好的比较。
就我个人观点看,脱离场景比较语言的性能,是没有意义的。
首先明确一点,APISIX 是基于 Nginx + Lua 的技术栈,只是外层代码用的是 Lua。所以如果要论证哪种网关性能更好,正确的比较对象是 C + LuaJIT 跟 Go 的比较。网关的性能的大头,在于代理 HTTP 请求和响应,这一块的工作主要是 Nginx 在做。所以倘若要比试比试性能,不妨比较 Nginx 和 Go 标准库的 HTTP 实现。
众所周知,Nginx 是一个 bytes matter 的高性能服务器实现,对内存使用非常抠门。随便举两个例子:
-
Nginx 里面的 request header 在大多数时候都只是指向原始的 HTTP 请求数据的一个指针,只有在修改的时候才会创建副本。
-
Nginx 代理上游响应时对 buffer 的复用逻辑非常复杂,是我读过的最为烧脑的代码之一。
凭借这种抠门,Nginx 得以屹立在高性能服务器之巅。
相反的,Go 标准库的 HTTP 实现,是一个滥用内存的典型反例。这可不是我的一面之辞,Fasth