内存诊断
网关内存上涨时,需要先知道是进程的哪一部分占用了内存,才能决定是加内存、限制流量,还是上报缺陷。AISIX 为此提供三种来源:
- Prometheus 端点上的内存指标。它们展示分配器和进程持有多少内存、进程所受的内存上限,以及每个进程内存储的占用情况。
- 按需采集的堆 profile。独立的、默认只绑定回环地址的诊断监听器上的
GET /debug/pprof/heap返回当前堆的 profile,可用go tool pprof读取。 - 自动写出的堆 profile。常驻内存接近内存上限时,网关会自动写出 profile,使证据在随后的内存溢出(OOM)终止后仍然保留。
三者默认全部开启,无需在控制面配置。它们在网关自身的启动配置中设置,开源网关和连接到 AISIX Cloud 的网关用法相同。
读取内存指标
这些指标与其他指标一起在指标监听器上提供。它们在 Prometheus 抓取端点时读取,从不在请求路径上读取,每个标签的取值都是固定集合。所有序列请参阅指标参考。
curl -sS "http://127.0.0.1:9090/metrics" \
| grep -E '^(aisix_allocator_bytes|aisix_memory_limit_bytes|process_resident_memory_bytes|aisix_component_|aisix_runtime_)'
这些指标分为以下几组:
| 指标 | 含义 |
|---|---|
aisix_allocator_bytes{stat} | 内存分配器统计的字节数:allocated(存活对象)、active、resident、mapped、retained 和 metadata。allocated 持续上升说明网关持有的对象变多;resident 远高于 allocated 说明存在碎片,或已释放的页尚未归还操作系统。 |
process_resident_memory_bytes 及其他 process_* 序列 | 内核视角下的进程,使用 Prometheus 标准进程采集器的名称,因此现有进程看板和告警无需修改即可使用。 |
aisix_memory_limit_bytes | 容器的内存上限,读取自 cgroup v2 的 memory.max 或 cgroup v1 的 memory.limit_in_bytes。进程没有上限时不存在该序列。 |
aisix_component_entries{component} 和 aisix_component_bytes{component} | 每个 进程内存储持有的条目数;存储能精确统计字节时,还给出字节数。 |
aisix_runtime_alive_tasks{runtime} 和 aisix_runtime_global_queue_depth{runtime} | 每个异步运行时上存活的任务数,以及排队但尚未被取走的任务数。runtime 为 control,并为每个每核一线程 Worker 增加 tpc-0 到 tpc-N。 |
分配器和堆 profile 相关序列仅由官方 AISIX 镜像中的 Linux 构建发出。process_* 序列在 Linux 上发出。
应当告警的是常驻内存占内存上限的比例:
process_resident_memory_bytes / aisix_memory_limit_bytes
找出占用内存的部分
先看组件指标,再与流量对照。每个存储指向不同的原因:
| 信号 | 可能原因 | 处理方式 |
|---|---|---|
aisix_component_bytes{component="in_flight_request_bodies"} 与 aisix_proxy_in_flight_requests 一同上升 | 大量大请求(例如内联图片或文档的请求 )在等待慢上游时被保留在内存中。 | 按这类请求规划内存并限制其并发。参见为大请求体规划内存。 |
aisix_component_bytes{component="guardrail_holdback"} 偏高 | 为输出安全护栏而保留的流式响应。 | 检查安全护栏的流式窗口。参见流式输出。 |
aisix_component_entries{component="metric_series"} 持续增长 | 标签基数:大量不同的标签值(例如 API Key、用户或模型)各自产生新的指标序列。 | 减少发布的标签。参见聚合与时间序列数量。 |
aisix_component_entries{component="exporter_queue"} 或 usage_event_queue 持续偏高 | 接收端缓慢或不可用:记录在内存中排队的速度超过投递速度。exporter 标签给出导出器名称。 | 检查目标端健康状况,以及导出器的丢弃和失败计数。 |
aisix_component_entries{component="log_queue"} 持续偏高 | 收集网关标准错误流的组件跟不上。 | 检查日志收集组件以及 aisix_log_lines_dropped_total。 |
aisix_component_entries{component="snapshot_pending_reclaim"} 持续大于 0 | 长时间运行的请求仍持有已退役的配置快照。在这些请求结束之前,每个快照都占用一整份配置的内存。 | 配置变更后短暂出现属于正常。若持续存在,请排查超长的流或卡住的请求。 |
aisix_runtime_alive_tasks 增长而流量不变 | 任务泄漏。 | 采集一份堆 profile,并连同指标历史一起上报。 |
其余组件统计的存储都受配置约束:response_cache 和 semantic_cache(本进程内持有的条目,不含 Redis 中的条目)、budget_cache、route_embedding_cache、ratelimit_local_keys(内存限流后端的键,使用 Redis 时为 0),以及 upstream_clients(为带有独立连接设置的服务提供方密钥构建的 HTTP 客户端)。
exporter 只在 component="exporter_queue" 上设置,其他组件为空。导出器删除后,其序列降为 0。
如果没有哪个存储能解释增长,或者 aisix_allocator_bytes{stat="allocated"} 上升而所有组件保持平稳,请采集堆 profile。
采集堆 profile
网关从启动起就对堆分配进行采样,因此可以直接从已经出现异常的网关上采集 profile,无需重启。平均每分配 2 MiB 记录一次分配及其调用栈。实测开销在网关基准测试的噪声范围之内。
从诊断监听器请求 profile,该监听器默认绑定 127.0.0.1:9091:
curl -sS -o heap.pb.gz "http://127.0.0.1:9091/debug/pprof/heap"
在 Kubernetes 中,该监听器绑定在 Pod 的回环接口上。请通过端口转发访问:
kubectl port-forward pod/<gateway-pod> 9091:9091
curl -sS -o heap.pb.gz "http://127.0.0.1:9091/debug/pprof/heap"
响应是当前使用中内存的 gzip 压缩 pprof profile。网关已经把调用帧解析为函数名,因此读取时不需要网关二进制文件。profile 不包含文件名和行号。用 go tool pprof 读取:
go tool pprof -top heap.pb.gz
go tool pprof -http=:8080 heap.pb.gz
在较大的堆上,采集一次 profile 需要数秒 CPU。同一时间只会运行一次采集:
| 状态码 | 含义 |
|---|---|
200 | 返回 profile。 |
429 | 另一次采集正在进行。请在其完成后重试。 |
501 | 本进程的堆采样已关闭,或当前构建不支持堆采样。 |
500 | 采集失败。网关会在日志中记录原因。 |
每次采集都计入 aisix_heap_profile_dumps_total{trigger="manual"},result 为 ok 或 error。
在内存溢出终止后取回堆 profile
OOM 终止会在任何人来得及请求 profile 之前结束进程,因此网关会在内存接近上限时自行写出 profile。网关每秒比较一次常驻内存与内存上限:容器的 cgroup 上限,或在没有上 限时取主机总内存。内存向上越过某个阈值时,网关为该阈值写出一份 profile;内存回落到该阈值以下 5 个百分点之后,该阈值才会再次触发。使用默认阈值 0.8 和 0.9 时,一个即将被 OOM 终止的网关会在达到上限的 80% 和 90% 时各留下一份 profile。
profile 写入 observability.heap_profiling.auto_dump.dir(默认 /var/lib/aisix/heap),文件名为:
<UTC timestamp>-<host name>-auto-<percent>.pb.gz
例如 20260928T101502.311Z-aisix-7d9f8-x2k4q-auto-90.pb.gz。在 Kubernetes 中,主机名即 Pod 名称。网关只为自己的主机名保留最新的 keep 份 profile(默认 5 份),并删除更早的文件;共享该目录的其他主机写出的 profile 不受影响。每份 profile 都计入 aisix_heap_profile_dumps_total{trigger="auto"},网关还会以 warn 级别记录其路径。
该目录必须比进程活得更久。在 AISIX Helm Chart 中,/var/lib/aisix 是一个 emptyDir 卷,在同一 Pod 内的容器重启(包括 OOM 终止后的重启)后仍然保留。从重启后的 Pod 中复制出 profile:
kubectl exec <gateway-pod> -- ls /var/lib/aisix/heap
kubectl cp <gateway-pod>:/var/lib/aisix/heap ./heap-profiles
emptyDir 卷随 Pod 一起删除,因此请在 Pod 被替换之前复制 profile。不使用该 Chart 时,请将 dir 指向在容器重启后仍然保留的卷。
如果该目录无法创建或写入,网关会在启动时记录一条警告,并且不会自动写出 profile。网关的其他功能不受影响。
为大请求体规划内存
请求等待上游期间,网关持有约两倍于其请求体的内存:一份是为重试和回退保留的已解析请求,另一份是发送出去的序列化请求体。对于合议模型,每个进行中的合议成员调用再增加一份序列化副本,因此一个合议模型请求持有约为请求体大小乘以(1 + 合议成员数)的内存。
对于携带大请求体(例如内联图片或文档)的请求,可按以下方式估算每个网关实例的内存:
内存 ≈ 基线 + 并发大请求数 × 请求体大小 × 2
例如,100 个并发请求、每个携带 20 MB 请求体,在其上游返回之前,需要在网关基线之上额外约 4 GB 内存。aisix_component_bytes{component="in_flight_request_bodies"} 显示当前持有的请求字节数,同一组件的 aisix_component_entries 显示持有这些字节的请求数。
要约束这部分内存,请在调用方 API Key 或模型上设置 concurrency 限制,控制同时运行的此类请求数。参见 API Key 与模型限流。使用默认的内存后端时,该限制按网关进程计数,每个实例各自最多放行到该上限。要在多个实例间执行同一个上限,请使用 Redis 后端。要直接拒绝过大的请求体,请参阅限制请求体大小。
配置内存诊断
默认值如下:
observability:
debug:
enabled: true
addr: "127.0.0.1:9091"
heap_profiling:
auto_dump:
enabled: true
thresholds: [0.8, 0.9]
dir: "/var/lib/aisix/heap"
keep: 5
| 字段 | 默认值 | 说明 |
|---|---|---|
observability.debug.enabled | true | 是否绑定诊断监听器。设置为 false 即关闭。 |
observability.debug.addr | "127.0.0.1:9091" | 诊断监听器地址。该监听器没有身份认证,因此地址不是回环地址时,网关会在启动时记录一条警告。地址无法绑定时,网关记录一条错误日志,并在没有该监听器的情况下继续启动。 |
observability.heap_profiling.auto_dump.enabled | true | 内存接近上限时是否自动写出堆 profile。 |
observability.heap_profiling.auto_dump.thresholds | [0.8, 0.9] | 内存上限的比例,每个值须大于 0 且不大于 1。 |
observability.heap_profiling.auto_dump.dir | "/var/lib/aisix/heap" | 自动 profile 的写入目录,不存在时自动创建。 |
observability.heap_profiling.auto_dump.keep | 5 | 每个主机名保留的 profile 数量,至少为 1。 |
与其他启动设置一样,除 observability.heap_profiling.auto_dump.thresholds 外,每个字段也可以通过环境变量设置,例如 AISIX_OBSERVABILITY__DEBUG__ENABLED=false。参见环境变量。
在 1.5.0 及更早版本中,网关无法从 AISIX_OBSERVABILITY__HEAP_PROFILING__AUTO_DUMP__THRESHOLDS 读取列表,设置该变量会导致网关无法启动。请在启动配置文件中设置 thresholds。
堆采样本身不是配置字段。要在不重新构建的情况下关闭它,请在网关进程上设置以下环境变量:
_RJEM_MALLOC_CONF=prof_active:false
关闭采样后,GET /debug/pprof/heap 返回 501,也不会再自动写出 profile。内存指标不受影响。
通过 Helm 配置
AISIX Helm Chart 通过环境变量配置网关。请用 extraEnvVars 设置这些字段:
extraEnvVars:
- name: AISIX_OBSERVABILITY__DEBUG__ENABLED
value: "true"
- name: AISIX_OBSERVABILITY__HEAP_PROFILING__AUTO_DUMP__ENABLED
value: "true"
- name: AISIX_OBSERVABILITY__HEAP_PROFILING__AUTO_DUMP__DIR
value: "/var/lib/aisix/heap"
- name: AISIX_OBSERVABILITY__HEAP_PROFILING__AUTO_DUMP__KEEP
value: "10"
在 1.5.0 及更早版本中,thresholds 无法通过 extraEnvVars 设置。不设置它时使用默认阈值。
Chart 没有为诊断监听器声明容器端口,且该监听器绑定在 Pod 的回环接口上。请通过到 Pod 的端口转发采集 profile:
kubectl -n <namespace> port-forward pod/<gateway-pod> 9091:9091
curl -sS -o heap.pb.gz "http://127.0.0.1:9091/debug/pprof/heap"
升级后的行为
升级到带有内存诊断的网关版本后,网关无需任何配置变更就会绑定 127.0.0.1:9091 并对堆分配进行采样。如果主机上的其他进程已占用该端口,网关会记录一条错误日志,并在没有该监听器的情况下继续处理流量。如需关闭该监听器,请设置 observability.debug.enabled: false。