文章

APISIX——南北向流量调度那些事儿

APISIX——南北向流量调度那些事儿

引言

Apache APISIX是一个基于OpenRestyNginx + LuaJIT)的云原生API网关。它把动态路由、限流熔断、鉴权、可观测性等能力都做成了插件,配置存储可以选择依赖etcd或者yaml,支持热更新、无需reload

参考APISIX的官方博客,聊聊为什么Apache APISIX选择NGINX+LuaJIT技术栈?

Apache APISIXNginx + LuaJIT的技术栈出发,Nginx + LuaJIT的技术栈给我们带来的,不仅仅是高性能。

思考一个问题,既然APISIX是基于Nginx开源版本,而Nginx并不支持动态配置,为什么Apache APISIX声称自己可以实现动态配置?是不是改了点东西?

是的,APISIX确实有在维护自己的Nginx发行版,不过Apache APISIX的大部分功能在官方的Nginx上就能使用。之所以能做到动态配置,全靠把配置放到Lua代码里面来实现。

以路由系统为例,Nginx的路由需要在配置文件里面进行配置,每次更改路由,都需要reload之后才能生效。这是因为Nginx的路由分发只支持静态配置,不能动态增减路由。

为了实现路由动态配置,Apache APISIX做了两件事,

  • Nginx配置文件里面配置单个server,这个server里面只有一个location。把这个location作为主入口,这样所有的请求都会走到这个地方上来。
  • Lua完成路由分发的工作。Apache APISIX的路由分发模块,支持在运行时增减路由,这样就能动态配置路由了。

你可能会问,在Lua里面做路由分发,会比Nginx的实现慢吗?

凡是对性能要求比较高的,APISIX会把核心代码用C改写。路由分发模块就是这么干的。路由分发模块在匹配路由时,会采用一个前缀树来匹配。而这个前缀树是用C实现的。感兴趣的读者可以看下代码:lua-resty-radixtree

完成了C层面上的前缀树匹配,接下来就该Lua发挥灵活性的时刻了。对于匹配同一前缀的各个路由,APISIX支持通过许多别的方式来进行下一级的匹配,其中就包含通过一个特定的表达式来匹配。尽管硬着头皮,也能在C层面上接入一个表达式引擎,但是纯C实现做不了非常灵活地自定义表达式里面的变量。

举个例子,下面是Apache APISIX用来匹配GraphQL请求的route配置,

1
2
3
4
5
6
7
8
9
10
11
{
        "methods": ["POST"],
        "upstream": {
            "nodes": {
                "127.0.0.1:1980": 1
            },
            "type": "roundrobin"
        },
        "uri": "/hello",
        "vars": [["graphql_name", "==", "repo"]]
}

它会匹配这样的GraphQL请求,

1
2
3
4
5
query repo {
    owner {
        name
    }
}

这里的graphql_name并非Nginx内置变量,而是通过Lua代码定义的。Apache APISIX一共定义了三个GraphQL相关的变量,连同解析GraphQL body在内不过62Lua代码。如果要通过Nginx C模块来定义变量,62行可能只不过是把相关方法的样板代码搭建起来,都还没有到真正的解析GraphQL的逻辑呢。

采用Lua代码来做路由还有一个好处:它降低了二次开发的门槛。

如果在路由过程中需要有特殊的逻辑,用户可以实现成自定义的变量和运算符,比如通过IP库匹配到的地理位置来决定采用哪条路由。用户只需要写一些Lua代码,这要比修改Nginx C module的难度小多了。

Apache APISIX里面,不仅仅路由是动态的,TLS服务端证书和上游节点配置都是动态的,而且无需修改Nginx,功能可以跑在官方的Nginx + LuaJIT技术栈上。当然通过修改Nginx,还实现了更多的高级功能,比如动态的gzip配置和动态的客户端请求大小限制。后续APISIX也会推行自己的Nginx发行版,这样开源用户也能轻松用上这些高级功能。

一句话理解它的定位:它本质上是一个增强版的OpenRestyAPISIX没有独立的常驻后台进程,流量由Nginxmaster/worker多进程模型处理,所有网关能力都是挂在请求生命周期上的Lua插件。

Nginx请求生命周期的11个阶段,如下,

Desktop View Nginx请求生命周期

APISIX的数据面和控制面

Desktop View 数据面和控制面

如上图所示,左右分别是APISIX的数据面(Data Plane)和控制面(Control Plane),

  • 数据面:以NGINX的网络库为基础(未使用NGINX的路由匹配、静态配置和C模块),使用LuaNGINX动态控制请求流量;
  • 控制面:使用etcd来存储和同步网关的配置数据,管理员通过Admin API或者Dashboard可以在毫秒级别内通知到所有数据面节点。

APISIX有什么?

看下APISIX控制面配置的元数据到数据面的数据流向,

Desktop View 元数据流向图

在数据面,微观上看进程列表(进入APISIX容器执行ps),一般情况下,只会有两类进程,分别是MasterWorker,而特权进程可选,不是网关必备主流程,这里不对特权进程做发散,感兴趣的话可以单独看下附录里边特权进程的简单介绍。

1
2
3
4
Master(Nginx原生)
├─ Worker #1(业务流量、处理请求、ngx.timer)
├─ Worker #2
└─ Privileged Agent 特权进程(OpenResty扩展)

从进程的视角观察数据面,流程如下,

Desktop View 从进程的视角观察控制面

1
2
3
4
5
Nginx Master(管理进程)
├─ Nginx Worker #1
└─ Nginx Worker #2
     ↓
     请求进来 → 执行内嵌Lua代码(APISIX核心逻辑、插件、路由、限流、鉴权)

Lua代码不是独立程序,是寄生在Nginx Worker请求回调钩子中的脚本。

所有网关能力挂在请求生命周期上,是什么意思?

Nginx在请求生命周期的11个阶段,提供一系列请求阶段钩子(rewriteaccessfilterlog等),

  1. 客户端发起HTTP请求到达Worker
  2. Worker按顺序触发各个阶段
  3. APISIX在这些阶段插入Lua逻辑,
    • access阶段:路由匹配、JWT鉴权、CORS、限流插件
    • upstream阶段:负载均衡、改写上游地址
    • log阶段:访问日志、监控上报

只要没有请求进来,Lua插件代码不会主动运行。

那定时任务、etcd watch配置监听是谁在跑?
很多人会疑惑,APISIX持续监听etcd变更、健康检查、定时清理缓存,这些后台任务难道不需要进程?

关键点来了,OpenResty提供ngx.timer定时器,定时器依然运行在现有Nginx Worker内部,不会新建独立进程。

  • 定时器 = Worker空闲间隙执行的一段Lua任务
  • 没有fork出新程序、没有独立后台进程
  • Worker退出,所有定时器、etcd长连接全部跟着消失

形象比喻,

  • Nginx Worker是一间办公室;
  • Lua插件、etcd监听、定时任务,都是办公室里员工顺带完成的工作;
  • 没有另外单独聘请一个专职后台人员(独立进程)。

反面例子帮助理解,

  • 如果有独立常驻进程:会多出一条进程,脱离Nginx生命周期,可以单独启停。
  • APISIX现状:关掉Nginx,整个网关直接消失;无法单独启停apisix业务模块。

底层载体永远是Nginx进程;网关只是运行在Nginx内部的Lua应用。对比Nginx原生,

  • 原生Nginx只有C模块;OpenResty给它增加运行Lua的能力;
  • APISIX = 一套极其庞大、封装好的Lua业务套件,跑在OpenResty之上。

Apache APISIX本质是运行于OpenResty之上的Lua应用,不存在脱离Nginx的独立常驻后台进程。
网关所有路由、鉴权、限流等能力,依托Nginx请求生命周期钩子执行;
配置监听、健康检查等后台任务依靠Worker内ngx.timer实现,复用现有Worker资源,不产生额外独立进程;
整个网关生命周期完全依附Nginx Master/Worker进程模型。

搭建APISIX测试环境

为了适配特定的需求,在实际生产中往往需要对APISIX进行二次开发,这里搭建的APISIX集群仅用于开发测试,包含了Consul(单机)、EtcdAPISIX和两个后端web实例。

准备docker-compose

Desktop View apisix docker compose示例

config.yaml 关键配置

APISIX 3.x的配置容易踩两个概念混淆的坑,先把它们区分清楚,

  1. 网关配置存储(路由/上游/消费者):由config_center决定,yamletcd
  2. 服务发现(这里选型用的Consul HTTP模式):由discovery模块决定,只负责动态拉取后端节点,不存储网关配置。

网关配置存储

原生APISIX关于网关配置存储的日常用法有两种,

  • config_center: etcd,路由持久化到etcd,配合内置UI才能保存生效;
  • 若用config_center: yaml,即DB-lessetcd模式(也叫yaml standalone独立模式),改路由刷新即丢失。

这两条路线面向两种完全不同的部署场景,一个定位分布式集群网关,一个定位轻量单机/边缘极简部署。Apache APISIX在诞生之初,目标就是云原生集群网关,所以etcd是原生标准模式;而DB-less是后续补充出来的轻量化方案,解决不想部署etcd的痛点。

  • etcd模式:依靠分布式KV实现多节点配置共享、Admin API动态管理、增量同步;
  • DB-less模式:零外部依赖、部署简单。

原生的两条路线的痛点也很明显,

  • etcd模式:网关强依赖etcd,增加运维复杂度,etcd不可用时网关无法启动;需要运维etcd集群;
  • DB-less模式:关闭Admin API,无法使用Dashboard可视化管理,无法原生支持动态API管理;缺乏集群同步能力,只能手工改文件,多节点配置难以统一;配置刷新为全量重载。

官方的落地建议

  1. 生产集群、多节点APISIX、需要动态调整路由,使用APISIX Dashboard可视化管理路由,选etcd(官方主推);
  2. 边缘设备、单机测试、离线环境、配置极少改动、不想运维etcd,选用DB-less,放弃UI动态保存路由的用法,全部配置在yaml文件维护;
  3. 禁忌场景
    • 多节点APISIX集群不推荐直接使用原生yaml模式,扩容多个APISIX实例时,各个节点内存路由不互通,极易出现集群严重不一致,各实例路由不统一;
    • 拥有数千条路由并且经常动态更新,不推荐yaml模式,全量加载会持续消耗CPU

不要混淆网关配置config.yaml和业务路由apisix.yaml

  • conf/config.yamlAPISIX自身启动参数(etcd地址、worker数量、admin密钥等)
  • apisix.yamlDB-less模式下存放路由、上游、插件规则

聚焦下原生DB-less模式,

原生的DB-less模式正确使用方式:所有路由、上游提前写死在apisix.yaml,修改路由必须改动yaml文件,再执行apisix reload。不能依赖UI临时新增。

直观看起来,这种模式在运维范式上退化至类似Nginx:持久规则依赖静态文件,无法通过UI/API永久保存路由。但它不属于完全退回Nginx,相较于Nginx,它重载代价较低,依然保留APISIX丰富的插件体系、更友好的声明式配置、支持运行时临时调试路由,只是舍弃了etcd提供的分布式动态配置能力。详细比对参见附录APISIX DB-less vs Nginx

DB-lessetcd模式的启动逻辑
APISIX启动时,一次性读取本地apisix.yaml文件,加载路由到内存。
这里有两条硬性约束,

  1. 启动后,在Dashboard/Admin API新增路由,仅保存在当前节点内存,不会写入任何持久化介质;
  2. 执行apisix reload/容器重启,APISIX重新加载本地yaml,内存里API新增的路由全部清空,只会保留yaml内写死的规则。
    1
    2
    3
    
    apisix.yaml = 唯一静态数据源
    任何通过Admin API动态添加的配置 ≠ 写入yaml
    APISIX不会自动把内存配置回写到yaml文件
    

为什么DB-less模式设计成不自动回写yaml

  • 官方设计初衷:DB-less面向GitOps声明式部署。
  • 期望流程:代码仓库维护apisix.yamlCI下发文件到网关 → reload生效。
  • 不设计自动回写,防止多人通过UI随意改动,破坏yaml单一可信源。

了解了两种路线的背景后,再聚焦到痛点,

原生etcd模式痛点,

  • APISIX网关启动强依赖etcd连通性;etcd宕机/网络不通,APISIX进程直接启动失败;
  • 所有数据平面节点运行时持续和etcd建立watch长连接,大规模集群场景etcd连接压力大;
  • 离线环境、边缘节点无法部署etcd时无法落地;
  • 缺少静态配置文件载体,难以直接对接GitOps配置入库、离线备份。

原生DB-less模式痛点,

  • 原生禁用Admin API,无法使用APISIX Dashboard,只能手工编辑yaml,运维效率极低;
  • 缺少统一配置管控中心,多APISIX节点需要人工同步yaml,极易出现节点配置不一致,无单一可信源;
  • 变更入口混乱:任何人可直接修改本地yaml,线上配置溯源困难,存在人为误操作风险;
  • 没有集中存储,配置分散在各个网关服务器,统一备份、审计困难。

思考一个问题,有没有办法通过二次开发,补上原生缺失的etcdyaml的回写通道,且不会破坏单一可信源,实现DB-less+Dashboard方式长期管理路由,解决上面所述的痛点呢?

先说答案,技术方案上是可以实现的。

运行时路由表仍然来自apisix.yamlconfig_center=yaml),只是yaml不再手工维护,而是由etcd自动dump生成(这时的yaml可信源降级成导出产物),同时做好快照版本。etcd数据面运行依赖变成控制面同步总线,将会是一个合适的想法。

先厘清单一可信源到底约束什么?

APISIX standalone(yaml)模式的官方初衷,核心不是yaml这个文件很重要,而是只允许一个写入方/一个权威源。声明式配置的价值在于,

  • 配置是GitOps/CI管出来的,可review、可回滚、可审计;
  • APISIX只读、不回写,所以运行态永远等于你commit的那份yaml
  • 不存在有人在运行时偷偷改了,和仓库里的不一致的问题。

所以真正要防的是双写(两个都能改、又互相覆盖的入口),而不是yaml不能被程序生成。

现在技术方案中,yaml不再依赖GitOps/CI里的配置,而是改由etcd控制面总线同步。每个节点的特权进程各自watch同一个etcd version key,各自把全量dump成自己本地的apisix.yaml,每个普通worker每秒轮询一次文件(pull模式)。由于是pull模式,这件事根本没有中心分发,自然也做到了集群多节点的数据一致同步。Dashboard只负责写etcd,和版本+1这一个触发信号。

原生yaml模式是只读静态文件,原生etcd模式是改即生效无版本。把两者拼起来,etcd当控制面同步总线+提交点(version key),定制APISIXetcd自动dumpyaml作为真正的运行时数据源,从而在保留yaml模式容灾优势的同时,获得了UI动态改配、批量原子发布、全局快照回滚等机制。

  • 提交发布:原生etcd模式是每条key变更各自watch、各自生效,批量发布时会经历中间不一致态。考虑在写完多条etcd后只提交一次,APISIX只在version变化时整体重生成yamlreload。批量发布具备原子提交语义。
  • 快照回滚:把整棵/apisix/*存成一条JSON记录(全量etcd快照 ),也能把任意历史快照整体写回etcddump版本,达到回滚的效果,原生etcd模式没有全局时间点回滚。
  • 容灾:同时考虑到容灾/可审查性,配置以纯文本yaml落地,且每个版本一个apisix_<ver>.yaml。即使etcd挂了,数据面仍能靠yaml独立启动运行,etcd不再是网关转发的硬依赖。

可以看到,针对etcd的痛点,能有以下收益,

  1. 解除网关运行时对etcd强依赖(DB-less核心收益)
    • APISIX只读取本地yaml启动,etcd故障不影响现有网关运行、不阻止网关启动。网关启动只依赖磁盘yaml文件,流量持续可用;etcd仅作为控制面存储,控制面故障只会阻断新增配置下发,不会断流量。
  2. 减轻etcd连接压力
    • 不再是NAPISIX节点同时watch etcd;仅1套同步程序watch etcd,同步程序负责生成yaml分发;数据平面网关不再直连etcd,大幅降低etcd长连接数量。
  3. 保留Dashboard可视化运维能力
    • 原生etcd模式才有可用Admin APIDashboard;这套架构继承该能力,不会因为改用文件加载而丢掉图形化管理。
  4. 天然产出声明式yaml文件
    • 配置可以持续落盘为标准apisix.yaml,支持Git归档、版本对比、离线灾备、环境复制。

针对DB-less的痛点,能有以下收益,

  1. 补齐可视化管理能力
    • 配置统一在Dashboard操作,复用官方成熟的路由编辑、测试、导入导出、权限能力,不再手写YAML
  2. 建立严格单一可信源,消除配置不一致风险
    • 规定etcd作为唯一可信源,所有yaml只是镜像;禁止直接修改节点本地yaml。所有变更收敛到Admin API,具备完整操作日志、审计;多节点yaml由同步程序统一分发,天然保证所有网关配置一致。
  3. 统一配置集中存储
    • etcd作为中央配置仓库,集中保存全量路由,便于全局检索、备份、灰度规划;yaml只是下发至数据平面的载体。

这里的etcd全量落回yaml实现方式,和不设计自动回写,防止多人通过UI随意改动,破坏yaml单一可信源的官方初衷冲突吗?

冲突不冲突,看权威方向,分下面两种情况,

  1. yaml是权威,etcd只是运行态缓存(yamletcd
    • 启动时把yaml灌进etcdAPISIX用,UI只读展示。这不冲突——yaml仍是唯一可信源,etcd是派生物。
  2. etcd/UI是权威,yaml是它的快照(etcdyaml,即全量落回)
    • 这时候yaml从可信源降级成了导出产物/备份。这确实和官方初衷冲突。

冲突点在于,

  • 谁要是直接改了yaml(比如运维手动改、Git里改),下一次全量落回就会被etcd覆盖,改动静默丢失;
  • 可信源事实上变成了etcdyaml只是它的投影。你没消灭双写,只是把权威从yaml挪到了etcd

所以,不做自动回写和全量落回是两回事。不设计自动回写来防止多人随意改动,本质是想保住etcd/UI不能成为权威。而全量落回yaml如果是etcdyaml方向,恰恰是让etcd成了权威。两种设计方案是矛盾的。

要既落回yaml、又不破坏单一可信源,通常得满足其一,

  • 落回的yaml重新进入Git/审批流程:UI只是生成配置的编辑器,产物必须走review才生效。这样权威仍在GitUI不是运行时热更新入口;
  • 明确yaml单向、只读方向:运行态永远从yaml生成,禁止人工直接改etcd之外的东西,UI关掉或只读;
  • 接受etcd为权威,把yaml明确定义为导出快照/灾备,并在文档和流程上禁止直接编辑yaml(靠约定而非机制,风险较高)。

最糟的组合就是:UI能改etcd、程序又全量落回yaml、同时还允许人改yaml——三个写入方,没有任何一个是明确权威,这才是真正破坏初衷的场景。

这时yaml从可信源降级成了导出产物/备份,谁要是直接改了yaml(比如运维手动改、Git里改),下一次全量落回就会被etcd覆盖,改动静默丢失。这在单向同步+etcd为唯一可信源的架构里是结构性风险,不是偶发bug。只要满足两个条件就必然发生,

  1. 同步是单向的:etcdyaml(导出),而yaml没有回写etcd的通道。
  2. 全量落回是覆盖式而非合并式:重新生成yaml时是按etcd当前状态整份重写,不比对、不保留本地差异。

在这两个前提下,任何绕过etcdyaml改动(运维手改文件、Git里改后部署)都是游离态的。它没进etcd,而etcd也不知道它的存在。下一次触发全量落回(重启、定时快照、手动dumpwatcher全量resync)时,etcd的状态会原样盖回yaml,改动静默丢失,而且没有任何冲突提示,因为系统根本不认为这是冲突,在它的世界观里etcd永远是对的。

关键在于把yaml从可信源降级成导出产物这个动作本身,同时也取消了它作为输入的合法性。降级后,

  • yaml改动有时生效,只是因为还没触发落回,这是最危险的。它给运维一种改yaml管用的错觉,埋到下一次resync才爆。
  • 时间窗口不可预测:如果落回是事件驱动(watcher resync)而非定时,丢失可能在任意时刻发生。

要不要接受这个风险,取决于你的定位,

  • 如果yaml确实只是备份/审计快照,这就是设计意图,不是缺陷。但需要在流程和工具上封死yaml的写入路径,比如文件设为只读、加显式头注释# GENERATED, DO NOT EDITCI里拒绝对该文件的手工diff、运维文档明确改配置只能走etcd/dashboard。风险来自人以为能改。把这条路堵死,风险就消失了。
  • 如果现实中运维确实需要能改yaml(GitOps场景):那yaml就不该被降级成纯导出产物,单向同步就是错的方向。需要的是双向或以yaml/Git为源的模型(yamletcd reconcile),或至少在落回前做三方diff,发现etcdyaml不一致时告警而非静默覆盖。

然而,这个思路并没有完全消除两种原生模式所有短板,只是互相取长补短,同时也引入新代价。

仍然遗留DB-less本身固有的机制短板(无法规避)

  • APISIX加载机制不变:yaml文件更新触发全量重载整套配置,不存在etcd原生的增量内存更新;上万条路由频繁变更依然存在CPU抖动;
  • 配置同步存在两级延迟:Dashboard写入etcd(毫秒)→同步程序导出yaml→文件分发到节点→APISIX 1s轮询检测文件变更;整体最大延迟高于原生etcd watch直连方案;
  • yaml存在文件半写风险,必须严格遵守#END规范、原子写文件。

新增二次开发带来的工程负担

  • 需要自行维护etcd watchyaml序列化 → 文件分发同步组件,属于自研代码,要处理,
    • etcd断连重连、版本对齐;
    • yaml序列化格式严格兼容APISIX standalone语法;
    • 文件原子下发,避免网关读到半截yaml
    • 一致性校验:定时对比etcd快照与各节点yaml,发现漂移自动修复;
    • 生产落地风险清单,以及对应的配套解决方案,详细参考附录网关存储优化方案:生产落地风险清单+配套解决方案;
  • 架构链路变长:用户→Dashboardetcd→自研同步器→yamlAPISIX,故障排查链路比原生架构更长。

总结下,该方案本质是控制面沿用etcd生态的管理优势,数据面采用文件无中心运行模式,牺牲一部分配置更新实时性与增量更新能力,换取网关更高的运行独立性。

原生etcd模式优势是动态API、集群一致性、增量同步,但网关强依赖etcd;原生yaml模式零外部依赖、网关不依赖中心组件,但缺失Dashboard、缺乏统一管控、变更入口散乱。通过搭建etcd做唯一可信源 + Dashboard管理 + 自研etcd->yaml单向同步通道 + APISIXDB-less方式运行,

  • 解决etcd模式短板:网关启动和运行不再强依赖etcd,边缘/离线场景可落地,降低etcd连接压力;同时保留集中配置存储;
  • 解决原生yaml模式短板:打通Dashboard可视化管理;收敛配置变更入口,确立单一可信源,解决多节点yaml同步混乱、无审计、随意修改文件的问题。

数据流单向约束:唯一可信源 = etcd,禁止反向同步。另外需要补充边界红线(强制规范),

  • 禁止任何人直接登录网关节点修改apisix.yaml
  • 禁止任何程序把yaml配置反向回写到etcd
  • 所有配置变更入口收敛:人员 → Dashboard → etcd
  • yaml只是etcd配置的镜像副本,不具备独立权威。

服务注册发现(APISIX与Consul)

Consul是用Go语言开发的,是一个支持多数据中心、分布式、高可用的微服务基础组件,支持健康检测与基于HTTPDNS协议的查询调用,服务注册发现与配置共享是Consul的重要功能。Consul内部采用Raft一致性算法来保证服务的高可用性,使用gossip协议来管理成员和广播消息,并且支持ACL访问控制,提供了一个现代的、灵活的、强大的基础架构,是作为微服务注册中心的最佳选择。

Consul的特性如下,

  • 服务发现:Consul的客户端提供对可用服务的查询功能,我们可以使用服务的标识去发现指定服务的所有提供者,在Consul内部可以通过DNS或者HTTP协议找到外部服务所依赖的服务;
  • 健康检测:Consul客户端提供健康检测功能,我们可以通过指定健康检测的地址和指定频率,及时得知一个服务是否处于健康状态,以避免将流量发送到不健康的服务节点;
  • 键/值存储:在应用程序中,用户可以根据自己的需要使用Consul提供的键/值存储。它用于实现动态配置、功能标记和协调等,通过简单易用的HTTP接口即可调用;
  • 多数据中心:Consul支持开箱即用的多数据中心机制。

对比etcdZooKeeperConsul三种服务发现工具,如下,

  • Consul支持分布式健康检测功能,可以指定任意节点进行检测,etcd不提供此功能;
  • Consul提供内置的Web界面管理功能,etcd不提供此功能;
  • Consul全面支持服务网格解决方案;
  • Consul内部使用Raft算法来保证一致性,比ZooKeeperPaxos算法更为简单、直接、有效;
  • Consul支持HTTPDNS协议接口。ZooKeeper的集成较为复杂,etcd只支持HTTP协议;
  • ZooKeeper临时节点在客户端断开连接时删除键/值项,相比心跳机制更复杂;另外,所有客户端必须保持到ZooKeeper服务器的活动连接,客户端调试较为困难;
  • Consul支持跨数据中心,采用不同的端口监听内外网的服务;ZooKeeperetcd均不提供多数据中心功能。

可以发现它们各有优劣。总体来说,选择Consul更为适合。Apache APISIX对接Consul有两种模式,分别是HTTP模式和DNS模式,这里用的是HTTP模式部署,它们之间的区别详情参见附录Kong.vs.APISIX对接Consul底层实现对比。

APISIXConsul的消费者,不会把自己注册进Consul。数据流是单向的,

1
2
3
4
web1/web2  ──(注册)──►  Consul  ◄──(拉取/查询)──  APISIX
              ▲                                    │
       注册脚本 / sidecar                     discovery.consul
                                             (只读服务目录)
组件Consul的行为
web1/web2被注册进去(被动)
注册脚本/sidecar主动写入注册
APISIX只读拉取,不注册自己

APISIX作为流量入口,客户端直接访问它,它无需被服务发现,所以APISIX注册到Consul的这个方向不存在、也不需要。

这里配置的是discovery(服务发现)。APISIX会周期性地去Consul拉取服务目录(fetch_interval: 3,表示每3秒拉一次),把结果缓存到本地discovery共享字典(discovery: 1m),供上游discovery_type=consul的路由使用。

1
2
3
4
5
6
discovery:
   consul:
      servers:
         - http://consul:8500
      fetch_interval: 3
      refresh_interval: 15

服务发现端(APISIX,已在config.yaml配好),APISIX启动后连到consul:8500,周期性拉取Consul的健康服务列表并缓存到共享内存。路由/上游里用discovery_type: consul + service_name 引用即可。

服务注册端(需要自己做),Consul不知道web1/web2的存在,得由服务方或脚本调用Consul注册API

正常服务都是服务自注册,本文例子中用的sidecar自动注册,最简单的HTTP API手动注册如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 注册 web1
curl -X PUT http://127.0.0.1:8500/v1/agent/service/register -d '{
  "ID": "web1",
  "Name": "my-web",
  "Address": "172.19.5.12",
  "Port": 8000,
  "Check": { "HTTP": "http://172.19.5.12:8000/web1.txt", "Interval": "10s" }
}'

# 注册 web2:同一个 Name 才能被负载均衡到一起
curl -X PUT http://127.0.0.1:8500/v1/agent/service/register -d '{
  "ID": "web2",
  "Name": "my-web",
  "Address": "172.19.5.13",
  "Port": 8000,
  "Check": { "HTTP": "http://172.19.5.13:8000/web2.txt", "Interval": "10s" }
}'

# 校验
curl http://127.0.0.1:8500/v1/catalog/service/my-web

注册要点

  • service_name必须匹配:路由里的service_name=Consul注册时的Name(示例是my-web);
  • 只发现健康实例:APISIX只会发现通过健康检查的节点;
  • 区分consulconsul_kv:前者基于Consul原生catalog/health接口,后者把节点写进KV,两者配置与注册方式不同,别混用;
  • 生产环境更推荐服务自注册(应用启动注册、退出反注册)或用sidecar自动注册,避免手动脚本。

APISIX怎么用这些数据?
APISIX里建upstream时用,APISIX就会从Consul拿到my-web下的两个实例做负载均衡。

创建路由引用Consul服务,也可以直接在DashBoard管理端配置,

1
2
3
4
5
6
7
8
9
10
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
  -H "X-API-KEY: nWWPdkrEQGdtKamXuJkJgdkARUvmSmJI" \
  -X PUT -d '{
    "uri": "/*",
    "upstream": {
      "service_name": "my-web",
      "discovery_type": "consul",
      "type": "roundrobin"
    }
  }'

多打几次代理入口,就能看到在web1web2之间轮询,

Desktop View 负载均衡

启动apisix集群

docker-compose和其他配置文件准备好后,就可以运行下面的命令,启动APISIX服务集群了,

1
2
3
4
mkdir -p ./apisix_log ./apisix_conf ./etcd_data ./consul_data
docker compose down -v      # 清理旧环境,避免旧数据冲突
docker compose up -d
docker compose logs -f apisix

下面是一些常用的入口,

服务地址备注
网关代理入口http://127.0.0.1:9080业务流量
APISIX内置UIhttp://127.0.0.1:9180/ui/输入admin key
APISIX Admin APIhttp://127.0.0.1:9180管理接口
Consul UIhttp://127.0.0.1:8500服务注册中心
web1直连http://127.0.0.1:9081/web1.txt后端1
web2直连http://127.0.0.1:9082/web2.txt后端2

简单测试流程

打开Apisix DashBoard控制台,看下有哪些元素,

Desktop View Apisix DashBoard内置UI

Desktop View Apisix DashBoard老版UI,有些选项设置不兼容

查看Consul注册中心,

Desktop View Consul主页

Desktop View Consul已注册服务

Desktop View Consul注册服务对应的节点

查看etcd的可视化数据,

Desktop View etcd ui

以上都确认好了之后,简单创建一个上游和一条路由规则,测试下联通性,

Desktop View 在老版本UI创建一个上游

这里使用服务发现,老版本UI这里有个问题,不兼容consul这种服务发现方式,于是去新版本UI修正下,

Desktop View 在新版本UI修正上游的服务发现类型

现在上游列表里有对应一条上游数据,

Desktop View 上游列表

上游创建好后,再对应增加一条路由规则,这里域名使用web.lvh.net,并关联上面创建好的上游,

Desktop View 创建路由规则

Desktop View 创建路由规则:设置上游

现在路由规则列表里有对应一条路由数据,

Desktop View 路由规则列表

现在再回来看看etcd里边的数据,

Desktop View 路由规则

Desktop View 上游

现在配置好了一条能访问到上游节点的路由规则,请求多打几次代理入口,

Desktop View 多请求几次

可以看到请求到后端节点,在web1web2之间轮询,

Desktop View 轮询日志

到这里,简单的hello world测试就结束了。

常用运维命令

1
2
3
4
5
6
7
8
9
10
11
# 进容器看有哪些内置插件(有对应 .lua 就是内置)
docker exec -it apisix ls /usr/local/apisix/apisix/plugins/ | grep "\.lua$"

# 查看主配置 / 日志
docker exec apisix cat /usr/local/apisix/conf/config.yaml
docker exec apisix tail -f /usr/local/apisix/logs/error.log

# APISIX 运维
docker exec apisix /usr/local/apisix/bin/apisix version
docker exec apisix /usr/local/apisix/bin/apisix reload
docker exec apisix /usr/local/apisix/bin/apisix test

附录

Nginx、Kong、Apache APISIX、Spring Cloud Gateway 横向对比

Desktop View 网关演进时间线

先理清定位,

  • Nginx:底层反向代理、Web服务器,不是完整API网关;
  • KongAPISIX:基于OpenResty(Nginx+Lua)的边缘API网关(流量入口、面向外网/南北向流量);
  • Spring Cloud GatewayJava生态微服务网关(偏向集群内部、东西向流量)。

架构比对

NginxOpenResty

  • 语言:C + Lua扩展(OpenResty
  • 配置存储:本地静态配置文件
  • 变更生效:必须reload/重启(reload有短暂开销)
  • 模型:无内置Admin API,无集群配置同步能力

OpenResty = Nginx + LuaJIT,原生Nginx不支持Lua

Kong Gateway

  • 底层:OpenResty(Nginx+LuaJIT)
  • 两种模式
    1. DB模式(传统):配置存PostgreSQL;节点定时轮询拉取配置(默认5s延迟)
    2. DB-less无库模式:静态yaml声明配置,更新需要重载
    3. Hybrid混合模式:分离控制面/数据面
  • 短板:DB模式引入数据库单点风险;大量高级插件企业版收费

Apache APISIX

  • 底层:OpenResty(Nginx+LuaJIT)
  • 配置中心:etcdK8s原生组件)
  • 机制:etcd watch毫秒级推送配置,节点无状态,完全不需要reload
  • 所有核心插件Apache2.0开源,无功能阉割;支持Lua/Go/Java/Python/Wasm多语言插件
  • 原生支持APISIX Ingress Controller,完美适配K8s

Spring Cloud Gateway

  • 语言:Java/ Netty响应式
  • 定位:Spring Cloud微服务生态内网关
  • 配置:配合Nacos/Apollo配置中心动态刷新
  • 短板:JVM开销大,性能远低于Nginx系;适合内网微服务,不适合超大并发外网入口

关键纬度比对

对比项Nginx(OpenResty)Kong OSSApache APISIXSpring Cloud Gateway
诞生年份2004201520192018
底层C语言事件驱动OpenResty(Nginx+Lua)OpenResty(Nginx+Lua)Java Netty Reactor
操作系统进程模型Nginx Master/Worker依附Nginx,无独立网关进程依附Nginx,无独立网关进程独立JVM进程
动态配置❌ 需reloadDB模式5s轮询;DB-less需重载etcd watch,毫秒热更新,无需reload✅ 配置中心动态刷新,无需重启网关进程
配置存储本地conf文件PostgreSQL/静态yamletcd(分布式KV配置中心(Nacos/Apollo)
配置同步方式修改文件 + reload节点Pull轮询(默认5setcd Watch Push推送配置中心主动拉取/事件通知
配置生效延迟reload耗时秒级延迟毫秒级视配置中心而定
后台定时任务能力薄弱ngx.timerWorker内)ngx.timerWorker内)JVM线程池
集群运维手动同步配置,无原生同步DB模式依赖数据库;扩容复杂数据面无状态,水平扩缩容极简依赖注册中心+配置中心
性能基准最高(裸代理)高,低于APISIX很高,接近原生Nginx中等,并发上限低
内置插件极少,需要自行开发lua基础插件免费;限流、OIDC等高级功能企业付费全部核心插件开源免费,100+内置基础Filter,自定义需Java开发
管理面板无官方DashboardKong Manager(企业版)官方apisix-dashboard完全开源无官方UI,需自研
多语言插件LuaLuaGoLuaGoJavaPythonWasm只能Java
协议支持HTTP/HTTPS/gRPC/WebSocketHTTP/gRPC/WSHTTP/gRPC/WS/MQTT/TCP/UDPHTTP为主
典型流量场景静态资源、负载均衡、CDN、简单反向代理(路由长期不变)传统企业API网关、对外开放API、存量海外业务云原生、K8s、高并发边缘入口、IoT、频繁动态路由Spring Cloud微服务内网网关
LicenseBSDApache2.0(高级功能闭源)Apache2.0完全开源Apache2.0
开源限制完全开源高级功能企业版收费全部核心插件开源免费完全开源

优缺点详解

Nginx / OpenResty

  • 优点
    • 性能天花板、极度稳定、内存占用极低;
    • 社区庞大,文档成熟;静态资源、SSL、缓存能力极强。
  • 缺点
    • 没有原生动态路由,改路由/限流必须reload
    • 缺少网关高阶能力:灰度、熔断、分布式限流、统一鉴权;
    • 集群部署需要自己做配置同步(ansible/git/confd)。

👉 适合:静态站点、简单负载均衡、CDN节点;不适合频繁变更路由的API网关场景。

Kong Gateway

  • 优点
    • 诞生最早,生态成熟,海外项目使用广泛;
    • 插件体系完善,支持多种认证、流量治理;
    • Hybrid模式适合大规模多集群。
  • 缺点
    • DB模式依赖Postgres,数据库成为故障点与扩容瓶颈;
    • OSS版本大量高级能力收费;
    • 配置变更存在秒级延迟;路由规模大时性能衰减明显。

👉 适合:海外业务、存量已经使用Kong、团队能接受商业授权;国内新项目越来越少选型。

Apache APISIX

  • 优点
    • 无数据库依赖,仅etcd,架构简洁;
    • 配置实时推送,毫秒生效,不用reload
    • 国内社区活跃、中文文档完善;Docker/K8s友好;
    • 开源版无功能阉割,内置Dashboard;支持TCP/MQTT物联网场景;
    • 节点无状态,容器扩缩容非常轻松(非常契合你docker-compose部署场景)。
  • 缺点
    • 相比Kong起步晚,部分小众第三方插件较少;
    • 需要额外维护etcd集群(单机测试可以单etcd)。

👉 适合:云原生K8s、容器化部署、内外网统一网关、高并发API入口、国内中小/大型企业首选。

Spring Cloud Gateway

  • 优点
    • Java技术栈无缝集成Spring Cloud;开发自定义Filter极其方便;
    • 支持Spring Security、服务发现(Nacos/Eureka)。
  • 缺点
    • JVM内存开销大,延迟更高,不适合外网高并发入口;
    • 重启/刷新有波动;缺少成熟的可视化运维面板。

👉 适合:微服务集群内部网关;不要直接暴露到公网。

清晰选型建议

  1. 公网边缘流量、高并发、容器/K8s、需要频繁调整路由
    • Apache APISIX(当前国内最优均衡方案)
  2. 已有PostgreSQL运维体系、海外业务、历史项目迁移
    • Kong
  3. 仅仅做静态资源、简单四层/七层负载,路由长期不变
    • Nginx/OpenResty
  4. Spring Cloud微服务集群,只做内网流量转发,团队全Java
    • Spring Cloud Gateway

最佳实践架构(常用分层)
外网:NginxSSL、静态资源、防CC) → Apache APISIXAPI治理、鉴权、灰度、限流) → Spring Cloud Gateway(微服务路由) → 业务服务

补充:APISIX vs Kong最容易踩坑的区别

  1. 配置同步机制
    • Kong DB模式:节点主动轮询拉取;APISIXetcd主动推送。路由上千条时差距明显。
  2. 运维成本
    • Kong需要维护PostgreSQLAPISIX只需要etcdK8s环境etcd属于基础设施,复用成本低)。
  3. 开源边界
    • Kong很多高级限流、OIDCRBAC在开源版没有;APISIX全部核心功能开源。

Desktop View 四大网关横向对比图

特权进程

Desktop View 特权进程的位置

回顾标准Nginx原生进程模型

标准Nginx只有两类进程,

  1. Master主进程
    • 读取配置、管理信号、创建/回收Worker、监控Worker存活
    • 不处理业务流量,不执行Lua
  2. Worker工作进程(多个)
    • 处理客户端HTTP请求
    • OpenResty下,Worker内部嵌入LuaJIT VM,执行所有请求链路Lua代码

原生Nginx:没有任何额外独立进程

OpenResty引入:Privileged Agent特权进程

背景限制
Nginx Worker有一个硬性约束:Worker运行在非特权用户(通常nginx用户),并且Worker内部禁止执行阻塞调用、不适合长时间阻塞任务。

常见受限场景,

  • 需要root权限执行操作
  • 需要长时间阻塞的任务(外部同步、定时批量任务、独立长连接)
  • 不希望被客户端请求事件抢占调度的后台任务

Worker里使用ngx.timer的短板,

  1. Timer依附Worker,如果Worker崩溃,任务中断;
  2. Timer受事件循环调度影响,高流量下被请求挤压;
  3. Worker权限受限,无法执行需要高权限操作。

因此OpenResty新增:特权进程privileged agent

特权进程和MasterWorker的层级关系,如下,

  1. Master进程fork出来,生命周期受Master管控;
  2. 独立进程,不属于Worker
  3. 默认可以配置为root/高权限用户运行(Worker一般降权);
  4. 不监听网络端口、不处理客户端流量。

重点说明,

  • Worker:处理HTTP请求,面向流量
  • 特权进程:纯粹执行后台Lua任务,不接收客户端连接

特权进程能做什么

  1. 执行需要系统特权的操作;
  2. 运行长期持续的后台任务、独立长连接;
  3. 全局定时任务,不受业务流量波动影响;
  4. 全局数据预热、外部资源同步、日志持久落地;
  5. 注意:特权进程只有一个!全局单实例,不会启动多个。

重要限制,

  • 特权进程没有Nginx请求上下文;
  • 不能使用大部分ngx.xxx请求相关APIngx.reqngx.varngx.exit等);
  • 只能使用无请求上下文的Lua API

和Worker内ngx.timer的核心对比

项目Worker ngx.timerPrivileged Agent特权进程
运行载体Nginx Worker进程内部独立OS进程,Master派生
数量每个Worker都可创建定时器全局仅1个进程
权限跟随Worker,一般低权限可配置高权限运行
是否处理流量同时处理HTTP请求+任务完全不处理客户端流量
调度影响高并发请求会挤压定时器执行不受业务流量影响
崩溃影响Worker退出,定时器消失独立生命周期,与Worker解耦
可用API完整Nginx Lua请求API禁用请求相关API

结合Apache APISIX场景理解

先澄清一个极易混淆的知识点: 默认情况下,APISIXetcd watch、健康检查,早期版本全部跑在Workerngx.timer里,不是特权进程。

APISIX什么时候使用特权进程?

  1. 部分全局一次性初始化任务;
  2. 某些需要独立后台、不占用Worker事件循环的批处理任务;
  3. 部分需要特权操作的插件能力;

绝大多数网关流量治理逻辑依然在Worker内部执行。

要点总结

  1. APISIX没有独立的apisix后台守护进程;
  2. 绝大多数后台任务跑在Worker ngx.timer
  3. 特权进程是可选的、单实例独立Lua进程,仅用于特定全局后台任务,不是网关必备主流程。

APISIX DB-less vs Nginx

大体思路相似,但不完全等价;是同源,都依赖静态文件作为唯一可信源,但能力上有明显区别,APISIX DB-less可以理解为,介于原生Nginxetcd集群模式中间形态。

  • DB-less(yaml)Nginx共同点:不能靠API动态持久变更配置,最终必须修改静态文件+重载;
  • APISIX DB-less相比原生Nginx依然保留网关高级能力(插件、Admin API、路由语法更强大),只是丢掉了etcd分布式动态持久化能力。

相同点(直观上,你所能感受到的退化)

从动态配置运维体验上看,确实退化到和Nginx同一套运维范式。

  1. 配置单一可信源 = 本地静态文件
    • Nginx:唯一来源 nginx.conf
    • APISIX DB-less:唯一来源 apisix.yaml
  2. 运行时通过API/UI新增的路由,只存在内存,不会落盘;重启/reload全部丢失
  3. 想要永久生效的规则,必须修改磁盘上的静态文件,再执行重载
  4. 天然不适合多节点集群动态同步:多实例需要保证所有节点yaml文件一致,需要外部工具同步配置(ansible/git/配置分发)

关键不同点(不能完全划等号)

重载代价不一样

  • Nginx
    • 修改confnginx -s reload
    • 会重建监听、重新解析所有配置,存在一定开销;大量配置下影响明显。
  • APISIX DB-less reload
    • APISIX内部机制优于原生Nginxapisix reload不会重建监听socket;只是重新加载yaml规则更新内存路由,平滑度优于传统Nginx reload

运行时临时调试能力不同

  • APISIX DB-less模式下,你依然可以调用Admin API/Dashboard临时新增路由,测试流量;只是不能持久化,一旦reload就清空。
  • 原生Nginx:运行时无法通过API临时添加路由,只能改文件。

形象区分,

  • Nginx:运行时完全不能临时改规则
  • APISIX DB-less:运行时可以临时改,但无法保存,重启归零

配置表达能力差距巨大

  • nginx.conf语法底层,灰度、权重、复杂限流、多鉴权、gRPC代理等需要大量C模块或OpenResty Lua开发;
  • apisix.yaml内置上百种网关插件,声明式配置路由、熔断、限流、跨域、灰度,开箱即用。

集群模式差异

  • Nginx集群:需要外部工具同步conf
  • APISIX DB-less集群:同样需要外部同步apisix.yaml

二者运维手段一致;但APISIX缺失etcd之后,失去原生集群配置自动同步能力。

分层对比表

维度原生NginxAPISIX DB-lessyaml)APISIX etcd模式
永久配置来源nginx.confapisix.yamletcd
想要永久变更路由修改conf + reload修改yaml + apisix reloadAdmin API/Dashboard直接操作,无需改文件
运行时能否临时新增路由❌ 不支持✅ 支持,仅内存生效✅ 支持,持久化保存
集群配置自动同步❌ 无原生能力❌ 无原生能力etcd watch自动同步
重载影响相对较重,重建监听较轻,只刷新路由规则不需要reload

小结

APISIX DB-less yaml模式,在运维范式上退化至类似Nginx:持久规则依赖静态文件,无法通过UI/API永久保存路由。

但不属于完全退回Nginx:它依然保留APISIX丰富的插件体系、更友好的声明式配置、支持运行时临时调试路由,只是舍弃了etcd提供的分布式动态配置能力。

最佳实践提醒

  1. 多节点集群、日常需要频繁调整路由 → 禁止使用DB-less,必须etcd模式;
  2. DB-less适合场景,
    • 简单单机、流量规则长期不变;
    • GitOps声明式交付,一切配置代码化,不允许任何人通过UI随意改动网关规则;
  3. 对于原生APISIX,不要指望DB-less + Dashboard长期管理路由,UI只能临时测试,重启全部丢失。

网关存储优化方案:生产落地风险清单 + 配套解决方案

风险1:yaml文件半写问题(原子写yaml)

风险描述

同步器直接覆盖apisix.yaml,若写入过程中断(进程崩溃、网络中断),产生残缺yaml

APISIX standalone依靠 #END 判断配置合法性,缺失标记则拒绝加载新配置,持续使用旧路由。

解决方案
  1. 原子写入范式(强制落地)
    • 先写入临时文件apisix.yaml.tmp
    • 完整序列化所有资源,文件末尾自动追加#END
    • fsync刷盘成功后,使用rename()原子替换正式文件
    • rename在同一文件系统下是原子操作,网关永远只会读到完整/旧版本配置
  2. 禁止直接vi/echo覆盖生产yaml
  3. 同步器写入完成后主动读取临时文件自检语法。

风险2:etcd ↔ 各节点yaml配置一致性漂移(一致性校验)

风险描述

多种场景引发不一致,

  • yaml分发网络丢包,部分网关节点未收到新版本yaml
  • 人为违规手动修改节点本地yaml
  • 同步服务重启期间漏掉部分etcd变更事件; 最终出现不同APISIX实例路由规则不一致,流量处理行为分裂。
解决方案
  1. 同步器每次生成yaml时,嵌入配置版本标识(写入yaml注释:etcd revision);
  2. 定时巡检任务:
    • 拉取etcd当前全局revision
    • 远程读取所有APISIX节点apisix.yaml内记录的revision
    • 对比,版本不一致立即触发告警,并自动重新下发最新yaml进行修复
  3. 增加审计告警:检测文件mtime非正常变更(人为篡改);
  4. 提供API一键触发全集群强制同步。

风险3:同步服务故障、分发失败(同步失败降级策略)

风险场景
  1. 同步服务进程崩溃、重启;
  2. 同步服务与etcd网络中断,watch断开;
  3. 同步服务与部分APISIX节点网络不通,yaml推送失败;
核心降级原则:存量流量不受影响,只阻断新配置下发

分层降级机制

  1. Watch断连处理
    • 同步器断开etcd后持续重试重连;重连成功后使用etcd revision补齐中断期间所有变更,避免丢失配置;
  2. yaml下发失败降级
    • 单节点推送yaml失败不阻塞整体流程,标记异常节点、持续重试,同时上报告警;
    • 绝不删除节点上已有的旧yaml,网关持续运行旧路由;
  3. 同步服务完全宕机终极降级
    • APISIX节点依靠本地已有yaml正常转发流量;业务无感知;
    • 运维收到告警后恢复同步服务即可,网关不需要重启;
  4. 灾备:同步服务做集群部署,防止单点故障。

风险4:错误配置下发全网,引发业务故障(配置回滚机制)

风险描述

Dashboard误操作路由、错误插件参数,同步器自动将错误yaml推送到所有网关,全集群路由异常。 原生架构缺少一键回滚通道。

完整回滚方案
  1. 配置快照持久化
    • 同步器每次生成新版本yaml,自动归档历史快照,关联对应的etcd revision;保留N个历史版本(例如30份);
  2. 支持灰度下发(可选高级能力)
    • 不一次性推送到所有节点,先推送灰度网关集群验证,验证通过再全量推送;
  3. 一键回滚能力
    • 运维选择历史revision,系统执行流程:
    • 选择历史快照 → 将历史配置写入etcdwatch触发同步器重新生成旧版本yaml → 分发至所有节点;
    • 关键点:回滚依旧走etcd(唯一可信源),不能直接推送旧yaml绕过控制面,保证单一可信源不被破坏;
  4. 前置校验
    • 同步器序列化yaml前执行Schema校验,非法配置拒绝写入etcd、拒绝下发。

风险5:APISIX Standalone 本身固有的短板(无法彻底消除,需要预案)

风险点
  1. 每次yaml更新,APISIX执行全量重载全部路由;上万条路由频繁变更时,CPU抖动、GC压力上涨;
  2. 配置同步链路变长:Dashboardetcd→同步器→分发→APISIX 1s轮询,最大延迟高于原生etcd直连模式;
缓解手段
  1. 控制配置变更频率,避免高频大量路由更新;
  2. 监控APISIX reload耗时、Worker CPU波动;
  3. 尽量将大量静态路由变更安排在业务低峰;

风险6:链路复杂度上升,故障定位难度增加

链路:客户端 → Dashboardetcd → 自研同步器 → yamlAPISIX

相比原生etcd直连架构,多了一层自研组件。

应对
  1. 全链路埋点日志:记录每次配置变更的操作人、revision、下发时间、各节点生效时间;
  2. 提供排查工具:快速查询某个节点当前生效配置版本,并和etcd基准对比;
  3. 同步器完善监控指标:watch事件数量、yaml生成耗时、分发成功率、校验不一致次数。

小结

风险类别核心危害核心措施
原子写yaml产生残缺配置,网关加载失败临时文件+rename原子替换,强制#END,写入后自检
一致性漂移多网关配置分裂,流量行为不一致携带etcd revision,定时全集群巡检+自动修复
同步服务故障新配置无法下发断线重试、服务集群化;失败不删除节点旧yaml
错误配置全网推送全集群业务故障版本快照归档、灰度下发、依托etcd实现标准回滚
全量重载性能抖动高频更新引发网关CPU冲高管控变更频率,低峰执行大批量改动
单一可信源破坏直接修改yaml导致配置溯源混乱权限管控、篡改告警、制度约束+程序巡检

Kong vs APISIX 对接Consul底层实现对比

Kong内部直接使用lua-resty-dnsConsul DNS8600端口)通信,依靠DNS协议做服务发现。

Apache APISIX对接Consul有两条独立路线。

DNS模式(和Kong思路一致)

APISIX同样支持Consul DNS服务发现(Consul 8600端口)

  • 底层依赖:lua-resty-dns-clientKong开源维护的库,内部封装了lua-resty-dns
  • 原理:Upstream配置discovery_type: dns,把Consul作为DNS服务器,通过查询service-name.service.consul获取所有健康实例。
  • 相似点:和Kong一样,基于Consul DNS协议,由Consul负责健康检测、过滤不健康节点;网关只做DNS解析。
  • 区别:Kong直接裸用lua-resty-dnsAPISIX封装了一层lua-resty-dns-client,自带缓存、重试、负载均衡逻辑。

配置示例(DNS模式)如下,

1
2
3
4
discovery:
  dns:
    servers:
      - "127.0.0.1:8600" # Consul DNS端口

上游引用示例,

1
2
3
4
{
  "discovery_type": "dns",
  "service_name": "training.rest.service.consul"
}

原生Consul HTTP发现(APISIX独有,推荐主流方案)

APISIX内置独立的Consul服务发现模块,不走DNS,直接调用Consul HTTP API8500端口)

  1. APISIX启动worker后周期性向Consul HTTP接口拉取服务实例列表;
  2. 本地缓存健康节点列表;
  3. 请求转发时直接使用本地缓存节点,不再走DNS解析;
  4. 支持监听变更、本地缓存持久dumptag过滤、datacenter指定等能力。

这是和Kong最大的差异, Kong官方没有独立Consul HTTP发现模块,只能走DNSAPISIX同时支持DNS模式+原生Consul HTTP拉取模式。

配置示例(HTTP原生Consul发现)如下,

1
2
3
4
discovery:
  consul:
    servers:
      - "http://127.0.0.1:8500"

上游引用示例如下,

1
2
3
4
{
  "discovery_type": "consul",
  "service_name": "training.rest"
}

核心差异汇总

项目KongApache APISIX
Consul通信方式DNS方案,lua-resty-dns查询Consul 8600方案ADNS(lua-resty-dns-client)
方案BHTTP API主动拉取(8500
协议DNS协议DNS/HTTP API二选一
健康检测责任Consul执行健康检查,DNS只返回健康节点两种模式:
DNS模式:健康检查由Consul负责
HTTP模式:依然依赖Consul健康状态
服务变更感知DNS TTL缓存被动更新HTTP模式支持定时轮询,实时性可控
底层Lua库lua-resty-dnslua-resty-dns-client(封装前者)

工程选型建议

  1. 如果想完全复刻Kong+Consul架构:APISIX使用discovery_type: dns,行为最贴近;
  2. 生产环境推荐APISIX使用discovery_type: consulHTTP模式),
    • 可以拿到更多Consul元信息(tag、权重、元数据);
    • 不受DNS TTL限制,节点更新更及时;
    • 便于排查,直接抓HTTP接口日志,比DNS报文调试简单。

参考

Apache APISIX 软件架构

有了 NGINX 和 Kong,为什么还需要 Apache APISIX

为什么 Apache APISIX 选择 NGINX+Lua 技术栈?

APISIX AI 网关介绍

Apache APISIX 博客

Apache APISIX 在 API 和微服务领域的探索

本文由作者按照 CC BY 4.0 进行授权

© ManShouyuan. 保留部分权利。

本站总访问量 本站访客数人次

🚩🚩🚩🚩🚩🚩