Apache APISIX 架构浅析
大家从网上肯定看到过关于Apisix性能高的文章,那么到底是如何实现的呢?
本文是分析也是自己学习《OpenResty从入门到实战》及Apisix官方文档的一个笔记
简单分析
先看一下官网的架构图
从图中可以看到APISIX是基于OpenResty与Nginx实现的。
这里需要注意OpenResty并不是Nginx的fork,也不是在Nginx的基础上加了一些常用库重新打包,而只是把Nginx当作底层的网络库来使用。
那具体是如何使用Nginx来作为网络库的呢?
其实很简单就是在Nginx.conf
中做简单的配置,让所有的流量都通过网关的Lua代码来处理。
注:Nginx默认不支持Lua的,这部分是OpenResty的能力
server {
listen 9080;
init_worker_by_lua_block {
apisix.http_init_worker()
}
location / {
access_by_lua_block {
apisix.http_access_phase()
}
header_filter_by_lua_block {
apisix.http_header_filter_phase()
}
body_filter_by_lua_block {
apisix.http_body_filter_phase()
}
log_by_lua_block {
apisix.http_log_phase()
}
}
}
# 这里只是一个简单的示意,完整的配置可以看到文末的nginx.conf文件
在这个示例中,监听了 9080 端口,并通过location /
的方式,把这个端口的所有请求都拦截下来,并依次通过access
、rewrite
、header filter
、body filter
和log
这几个阶段进行处理,在每个阶段中都会去调用对应的插件函数。其中,rewrite
阶段便是在apisix.http_access_phase
函数中合并处理的。
这里是一份完整的Nginx的配置文件:APISIX Nginx.conf
这里补充下OpenResty Phases
那Lua层面是如何保证高性能的呢?
协程+事件
在OpenResty层面,Lua的协程会与Nginx的事件机制相互配合。如果Lua代码中出现类似查询 MySQL 数据库这样的 I/O 操作,就会先调用Lua协程的 yield 把自己挂起,然后在Nginx中注册回调;在 I/O 操作完成(也可能是超时或者出错)后,再由Nginx回调 resume 来唤醒Lua协程。这样就完成了Lua协程和Nginx事件驱动的配合,避免在Lua代码中写回调。
LuaJIT
我们先来看下LuaJIT在OpenResty整体架构中的位置:
OpenResty的worker进程都是fork master进程而得到的,其实,master进程中的LuaJIT虚拟机也会一起fork过来。在同一个worker内的所有协程,都会共享这个LuaJIT虚拟机,Lua代码的执行也是在这个虚拟机中完成的。
标准 Lua 和 LuaJIT
其实标准Lua出于性能考虑,也内置了虚拟机,所以Lua代码并不是直接被解释执行的,而是先由Lua编译器编译为字节码(Byte Code),然后再由Lua虚拟机执行。
而LuaJIT的运行时环境,除了一个汇编实现的Lua解释器外,还有一个可以直接生成机器代码的JIT编译器。开始的时候,LuaJIT和标准Lua一样,Lua代码被编译为字节码,字节码被LuaJIT的解释器解释执行。
但不同的是,LuaJIT的解释器会在执行字节码的同时,记录一些运行时的统计信息,比如每个Lua函数调用入口的实际运行次数,还有每个Lua循环的实际执行次数。当这些次数超过某个随机的阈值时,便认为对应的Lua函数入口或者对应的Lua循环足够热,这时便会触发JIT编译器开始工作。
JIT 编译器会从热函数的入口或者热循环的某个位置开始,尝试编译对应的Lua代码路径。编译的过程,是把LuaJIT字节码先转换成LuaJIT 自己定义的中间码(IR),然后再生成针对目标体系结构的机器码。
所以,所谓LuaJIT的性能优化,本质上就是让尽可能多的Lua代码可以被JIT编译器生成机器码,而不是回退到Lua解释器的解释执行模式。
注意
- LuaJIT并不完备,存在NYI(Not Yet Implemented)问题
- LuaJIT 的作者目前处于半退休状态
- OpenResty用的LuaJIT是LuaJIT自己维护的分支
语句摘录
在学习《OpenResty从入门到实战》的过程中对于一下几句话深有感触,这里摘录一下与大家共勉:
- 在我看来,明白一个技术为何存在,并弄清楚它和别的类似技术之间的差异和优势,远比你只会熟练调用它提供的 API 更为重要。这种技术视野,会给你带来一定程度的远见和洞察力,这也可以说是工程师和架构师的一个重要区别。
- 任何技术课程的学习,都不能代替对官方文档的仔细研读。这些耗时的笨功夫,每个人都省不掉的。
- OpenResty 现在的官方文档只有英文版本,国内工程师在阅读时,难免会因为语言问题,抓不住重点,甚至误解其中的内容。但越是这样,越没有捷径可走,你更应该仔细地把文档从头到尾读完,并在有疑问时,结合测试案例集和自己的尝试,去确定出答案。这才是辅助我们学习OpenResty的正确途径。
- 对待技术的选择,我们可以有倾向,但还是不要一概而论绝对化,因为并没有一个可以适合所有缓存场景的银弹。根据实际场景的需要,构建一个最小化可用的方案,然后逐步地增加,是一个不错的法子。
OpenResty vs Envoy 底座比对
随着云原生的普及,Kubernetes技术栈的南北向流量网关也开始普及:南北入口网关选型
虽然从目前的南北向网关功能来看还不如以OpenResty为底座的Kong或者APISIX,但云原生社区明显更加活跃。
下面从Contributors、Star、Commits、Releases四方面来分析比对(数据取值时间:2025.03.22):
- Contributors
- OpenResty:35
- Envoy:1227
- Star
- OpenResty:12.9k
- Envoy:25.7k
- Commits
- OpenResty:1746
- Envoy:24125
- Releases
- OpenResty:16
- Envoy:196
从目前功能来看Kong或者APISIX能力更全,但从长远来看,个人更看好Kubernetes Gateway API相关的网关。
现在感觉Cloudflare也放弃了OpenResty,转到了Pingora:how-we-built-pingora-the-proxy-that-connects-cloudflare-to-the-internet
Pingora 并不是一个工具,而是一个库。