




















大约十年前,部门内部有一套 ESB, 一套网关服务,当时网关服务用的 Python (Django 框架), 处理了一些基本的认证/流控逻辑,但是无法支持高并发并且经过网关的性能损耗很大
在 2019 年,我们使用 Golang 重写了网关服务,性能大约是原来的 34 倍
至于选择 Golang 的原因,主要是因为:
(我们的网关上线一年多之后,Apisix 团队有到我们内部做过分享,当时切到其他项目救火了并没有去听)
当时技术选型没有使用 gin, 而是 chi (轻量,100% compatible with net/http ) + std reverse proxy + dbless 模型 (借鉴 Kong 的概念)
做了两个大版本:
(这个版本的一直非常稳定,并且性能比 apisix 版本还好。得益于 goroutines / channels 以及无处不在的缓存)
然后在 2022 年,我们又遇到下一个问题:扩展性,我们有越来越多的需求
但是
在 2019 我切换去做权限系统,搞完之后又去接用户系统,到了 2023 四月份,由于某些原因,切回来接手新版的基于 apisix 的网关
接近八个月时间,很大部分时间在做架构调整以及重构,并且花费了很长一段时间验证新版本的稳定性,遇到了蛮多问题
最近在整理工作文档,汇总一下
简单梳理总结一下
原先的项目拆分的非常细碎,通过 SDK 共享代码,通过 API 做数据交互
(这块花了很多时间,才做到 production ready, 主要是又得在开飞机的时候换引擎)
基本按照 Upgrade Guide 阅读,评估后直接升级到 3.2.1
主要的变更还是配置文件,顺带还把原先配置文件的生成方式重写,直接 helm 生成最终用的文件,抹掉了在容器启动时各种 sed/subst 之类的操作
其实后来还尝试升级到 3.2.2, 当时以为是一个小版本,结果合入了一个 pr #9456 feat(config_etcd): use a single long http connection to watch all resources, 导致了 bug: route 404 after upgrade to 3.2.2, 原因是etcd prefix中带了-, 在这个 fix: can’t sync etcd data if key has special character 中修复了
带了 9456 这个特性的版本都需要关注下这个 PR 后面带的一批 bugfix (应该有四五个了,并且都比较重要,所以建议用最新版)
apisix 3.2.1 已经合入 #8434 feat(prometheus): support collect metrics works in the priviledged agent, 所以理论上由于 prometheus 带来的性能问题应该已经解决了。(相关文章 从 1 秒到 10 毫秒!在 APISIX 中减少 Prometheus 请求阻塞)
踩坑 1: 最早我们网关的 prometheus 自定义指标中,使用了 path 而不是 matched_uri作为 label, 这样会导致/metrics中记录数爆炸式增长,带来性能问题
踩坑 2: 由于我们的 route 数量很多,官方的 local DEFAULT_BUCKETS = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 30000, 60000} 是写死的,非常大导致记录数很大。所以我们提了个 issue feat: The Prometheus plugin can now allow users to configure the DEFAULT_BUCKETS, 后来在 #9673 实现
踩坑 3: 某一天线上某个环境的 apisix 被 KILL 了,影响到业务,后来定位到是 CPU 超过了 limits 被 k8s 杀掉; 而这个环境的请求量并不大,通过分析我们发现是 prometheus 特权进程在处理/healthz时把 CPU 给打满了,bug: high cpu usage after prometheus.lua report ’no memory’, 原因还是 官方的 metrics 记录数过多
踩坑 4: 目前我们还遇到一个疑似内存泄露问题,看着也是跟 prometheus 插件有关系 help request: 3.2.1 memory leak, 暂时没有解决,生产环境分配比较大的内存 (涨到一定大小之后就趋于稳定了)
发布后,有业务反馈其原先正常的路由目前被匹配到了另一个路由上
apisix:
router:
http: radixtree_uri_with_parameter
在这种类型的 radixtree 中,最长路径匹配实际上并不是确定的,跟路径被加入树中的顺序有关,bug: Adding routes to a Radix Tree in a different order can lead to the same URL matching the first added route instead of the longest path match
这个问题目前官方还没定位修复,我们目前是通过写入路由配置时,计算 priority,使得其看起来像最长路径匹配 通过 priority 解决最长路径匹配失败:calculateMatchSubPathRoutePriority
DNS 解析失败之后,有报错日志; 然后 DNS 服务恢复了,apisix 也一直无法将请求进行转发
报错:
2023/04/20 09:08:02 [error] 68#68: *151026556 [lua] resolver.lua:47: parse_domain(): failed to parse domain: aaa.com, error: failed to query the DNS serve
r: dns server error: 2 server failure, client: 1.1.1.1, server: _, request: "POST /api/v1/xxxxx HTTP/1.0", host: "aaa.com"
2023/04/20 09:08:02 [error] 68#68: *151026556 [lua] upstream.lua:79: parse_domain_for_nodes(): dns resolver domain: aaa.com error: failed to query the DNS
server: dns server error: 2 server failure, client: 1.1.1.1, server: _, request: "POST /api/v1/xxxxx HTTP/1.0", host: "aaa.com"
2023/04/20 09:08:02 [error] 68#68: *151026556 [lua] init.lua:540: http_access_phase(): failed to set upstream: no valid upstream node, client: 1.1.1.1, server: _, req
uest: "POST /api/v1/xxxxx HTTP/1.0", host: "aaa.com"
触发条件:route 有 upstream(upstream 中是域名), 并且 router 关联到了 service, 并且 service 中有 upstream
相关 issue: bug: dns resolution did not resume immediately after the dns server resume
这个问题官方暂时没有定位修复,解决方法:自行 patch parse_domain_for_nodes
并且切换到更为稳定的 DNS 服务
我们有几万个 route, 变更非常频繁,我们发现在管理端变更时,会导致 apisix 的性能抖动 help request: Is the radix tree rebuilt every time any route is updated?
后来通过阅读源码发现,每次 watch 到变更,都会导致 radixtree 重建,并且这个重建是同步的,会导致性能抖动
官方后来有个 feat: increment route update for radixtree host uri, radixtree uri and radi… 做增量更新优化
我们暂时的解决办法:如果有变更,定期重建 radixtree(而不是实时), 并且打散到一定范围的时间内,避免整体服务性能抖动,具体参考 radixtree_uri_with_parameter_rebuild_with_interval
相关 issue help request: when use Chinese word as uri parameter got 404
apisix 上下文中使用的路径是decode过的,但是下层 radixtree 使用的是encode过的,会导致这类路径匹配 404
目前我们暂时用 10561 这种方式修复,但是这样做看着比较低效 (我们压测完发现差别不大,线上直接先 patch 了)
发布时,正在处理的请求会受到影响 Is the apisix support preStop hook? for graceful shutdown
除了这个,还遇到 ipvs+linux 内核版本会导致滚动更新时,还会有持续的流量进入到正在 terminate 的 pod 中
生产环境上线后,etcd 会定期出现内存使用量告警,突然飙升,伴随着大量的 read 行为
查看 etcd 配置以及阅读 apisix 源码发现,etcd 当前配置 5 分钟定时 compacted 一次,存在发布变更的话,apisix watch 到 event 发现 err=compacted, 会导致 apisix 全量拉取数据,此时会导致大量的 read 行为,导致 etcd 内存飙升
所以建议 apisix 依赖的 etcd 配置 compaction mode 改成 revision, 相关文档
变更 etcd 配置:
auto-compaction-mode: revision
auto-compaction-retention: "1000"
自定义插件,通过增加 batch-processor 解决 (本质上就是将 json encode 转移到了 nginx timer 里)
灰度过程中,有用户反馈拼接的参数没有生效,而在原来 Golang 版本网关中是正常的
排查发现,我们使用的是 Golang 1.6, 这个版本中,支持同时使用&和;作为 QueryString 的分隔符,然后这个特性在 Golang 1.7 中被去除了 Go 1.17 Release Notes: URL query parsing
所以,存量的请求中,原先有一些请求使用的是;作为分隔符,灰度到 Apisix 后就失效了
最后我们通过一个自定义插件,向前兼容了这批路由,原理很简单,把 ; 换成 & 再放回去解析
local new_args = string_replace(ctx.var.args, ";", "&")
core.request.set_uri_args(ctx, new_args)
有应用反应原先使用 nginx 作为反向代理的时候正常,接入 apisix 之后偶发 502
nginx 默认的配置 proxy_http_version 是 HTTP/1.0;
即没有配置的情况下,默认是 HTTP/1.0
但是 apisix 的默认配置
proxy_http_version 1.1;
keepalive_timeout 60s;
这就意味着,如果后端服务开启了 keepalive, 但是配置的 timeout 小于 60s, 那么就会出现偶发 502
实际验证中,对于 gunicorn 以 gevent/gthread 方式启动的服务,将默认的2s改成65s也不能规避这个问题,只能通过 --keep-alive 0 关闭 keepalive; 相关文档
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。