我们的微服务架构用了API网关作为统一入口,所有的请求都要经过网关转发到后端服务。

最近流量上来之后,网关的性能成了瓶颈。P99延迟一度超过800毫秒,经常超时,后端服务反而很空闲。经过一周的排查和调优,我们把网关的P99延迟降到了150毫秒左右,下降了80%以上,吞吐量也提升了3倍。

这篇文章就来分享一下API网关性能调优的实战经验。我们用的是Spring Cloud Gateway,但大部分思路和方法对其他网关(比如Kong、Nginx、APISIX)也是通用的。

问题现象

先说说当时的问题现象。

我们的网关部署在Kubernetes上,4个Pod,每个Pod 4核8G。平时流量不大的时候还好,一到高峰期(比如中午和晚上的促销活动),网关的延迟就急剧上升。

具体表现是:

  • P50延迟大概50毫秒,还算正常。
  • P95延迟超过300毫秒,P99延迟超过800毫秒。
  • 网关的CPU利用率不高,只有40%左右,但请求排队严重。
  • 后端服务的CPU和延迟都很正常,说明瓶颈在网关本身。
  • 偶尔会出现502和504错误,是网关转发超时导致的。

刚开始我们以为是流量太大,加了Pod副本数,但延迟没有明显下降。这说明不是容量不够,而是有其他瓶颈。

于是我们开始了系统性的性能排查和调优。

第一步:建立监控和基准

调优的第一步是建立完善的监控,搞清楚时间都花在哪里了。

我们在网关上加了详细的监控指标,包括:

  • 请求总量、成功率、错误率
  • 延迟分布(P50、P90、P95、P99)
  • 每个路由的请求量和延迟
  • 连接池的使用情况(活跃连接、等待连接、连接创建数)
  • JVM的GC情况(GC次数、GC时间、堆内存使用)
  • 线程池的使用情况(活跃线程、队列长度、拒绝次数)
  • 网络IO和CPU使用率

同时,我们做了基准测试。用压测工具模拟不同的并发量,测网关在不同负载下的性能表现,建立一个性能基线。

有了这些数据之后,我们很快发现了几个问题。

第一个问题是连接池配置不合理。网关到后端服务的HTTP连接池,最大连接数只有50,而高峰期并发请求远超过50,导致大量请求在等待连接。这是延迟高的主要原因之一。

第二个问题是JVM的GC太频繁。因为堆内存设置不合理,Young GC每分钟好几次,每次GC虽然时间不长,但会导致请求停顿,拉高P99延迟。

第三个问题是路由规则太复杂。有一些路由用了复杂的Predicate和Filter,每个请求都要做大量的匹配和判断,增加了处理时间。

第四个问题是没有合理的缓存。一些不常变化的配置数据(比如路由规则、限流规则),每次请求都从配置中心拉取,增加了延迟。

找到了问题之后,我们开始针对性地调优。

第二步:连接池调优

连接池是网关性能最常见的瓶颈。

Spring Cloud Gateway底层用的是Reactor Netty,HTTP连接池的默认配置比较保守。默认的最大连接数是根据CPU核数来算的,4核机器默认是几百,但实际的连接获取超时和连接空闲超时设置得不太合理。

我们做了这几个调整。

第一,增大最大连接数。根据后端服务的处理能力和并发量,把最大连接数调到了500。这个值不是越大越好,太大会给后端服务造成压力,需要根据压测结果来定。我们的后端服务每个实例能处理200并发,有5个实例,总共能处理1000并发,所以500的连接数是合理的。

第二,调整连接获取超时。默认的连接获取超时是45秒,太长了。如果连接池满了,请求会等45秒才超时,这会导致大量请求堆积。我们把它调到了3秒,获取不到连接就快速失败,由客户端重试或者降级。

第三,调整连接空闲超时。默认的连接空闲超时是永久,意味着连接一旦建立就不会释放。这在后端服务重启或者网络变化的时候会有问题,可能会用到已经失效的连接。我们把空闲超时调到了30秒,定期释放空闲连接。

第四,开启连接池的metrics。监控连接池的活跃连接数、等待连接数、连接创建数。这样可以实时了解连接池的使用情况,及时发现问题。

调整之后,连接等待的问题基本解决了,P99延迟下降了200毫秒左右。

第三步:JVM调优

JVM的GC是影响P99延迟的重要因素。特别是Young GC,虽然每次时间不长,但在高并发的时候,GC停顿会导致请求延迟飙升。

我们的JVM参数是默认的,堆内存只有4G(容器8G内存),而且GC用的是默认的Parallel GC。

我们做了这几个调整。

第一,增大堆内存。把堆内存从4G调到了6G,减少GC的频率。容器有8G内存,留2G给堆外内存和系统使用,是合理的。

第二,换用G1 GC。G1 GC适合大内存、低延迟的场景,能把GC停顿控制在一个可预期的范围内。我们设置了-XX:MaxGCPauseMillis=50,目标是把GC停顿控制在50毫秒以内。

第三,调整新生代比例。G1 GC会自动调整新生代的大小,但我们设置了一个初始值,让新生代不至于太小。新生代太小会导致频繁Young GC,太大会导致单次GC时间变长。

第四,开启GC日志。用-Xlog:gc*参数开启GC日志,方便分析GC的情况。同时把GC日志接入监控系统,实时监控GC的频率和停顿时间。

调整之后,Young GC的频率从每分钟好几次降到了每几分钟一次,单次GC停顿也控制在了30毫秒以内。P99延迟又下降了100毫秒左右。

第四步:路由和Filter优化

网关的路由规则和Filter链,也是影响性能的因素。

Spring Cloud Gateway的每个请求都会经过一系列的Filter,比如鉴权、限流、日志、请求改写等。如果Filter太多或者太复杂,会增加请求的处理时间。

我们做了这几个优化。

第一,简化路由规则。我们有一些路由用了复杂的Path匹配和Header匹配,每个请求都要遍历所有路由规则做匹配。我们把路由规则做了整理,去掉了不必要的复杂匹配,用更精确的Path前缀匹配。同时把高频路由放在前面,减少匹配的次数。

第二,优化Filter链。我们审查了所有的全局Filter和路由Filter,去掉了不必要的Filter,合并了功能重复的Filter。比如有两个Filter都在做请求日志,我们合并成了一个。有一些只在特定路由需要的Filter,从全局Filter改成了路由Filter,减少不必要的执行。

第三,异步化处理。有一些Filter在做同步的IO操作(比如调用远程服务校验权限),会阻塞请求线程。我们把这些操作改成了异步的,用WebClient或者响应式的方式调用,不阻塞线程。这样同样的线程数能处理更多的并发。

第四,缓存配置数据。路由规则、限流规则、鉴权的公钥等配置数据,不需要每次请求都从配置中心拉取。我们加了本地缓存,设置合理的过期时间(比如30秒),减少远程调用。配置变更的时候,通过配置中心的推送机制主动刷新缓存。

这些优化做完之后,单个请求的网关内部处理时间从平均20毫秒降到了5毫秒左右。

第五步:限流和熔断

限流和熔断不是直接提升性能的手段,但能保护网关和后端服务,避免在高负载下雪崩。

我们在网关上做了这几个配置。

第一,全局限流。设置了整个网关的QPS上限,超过的请求直接拒绝,返回429状态码。这能防止网关被打垮,保证在容量范围内的请求能正常处理。

第二,路由级限流。对每个后端服务设置单独的限流阈值,根据后端服务的处理能力来定。这样即使某个后端服务出问题,也不会影响其他服务。

第三,熔断。对后端服务的调用设置了熔断机制,当错误率超过阈值(比如50%)的时候,自动熔断一段时间,不再调用有问题的后端服务,直接返回降级结果。等后端服务恢复之后,再自动恢复。

第四,超时控制。对每个路由设置了合理的超时时间(连接超时1秒,响应超时5秒),避免因为后端服务慢导致网关的连接被长时间占用。

有了限流和熔断之后,网关在高峰期的稳定性提升了很多,502和504错误基本消失了。

第六步:操作系统和网络调优

除了应用层面的调优,操作系统和网络层面的调优也很重要。

我们做了这几个调整。

第一,调整文件描述符限制。默认的文件描述符限制是1024,高并发下会不够用。我们把它调到了65535。

第二,调整TCP参数。调整了tcptwreuse、tcpfintimeout、net.core.somaxconn等参数,优化TCP连接的处理。特别是TIMEWAIT状态的连接回收,高并发下会有大量的TIMEWAIT连接,需要调整参数让它们更快回收。

第三,开启TCP快速打开(TCP Fast Open)。减少TCP握手的开销,降低连接建立的延迟。

第四,调整网卡的队列和中断。把网卡的中断绑定到特定的CPU核,避免中断在多个核之间跳转,提升网络处理的效率。

这些操作系统层面的调优,虽然单个的提升不大,但加起来也有10%到20%的性能提升。

第七步:压测验证和持续监控

每做一项调优,我们都会做一次压测,对比调优前后的性能数据,确认调优有效,而且没有引入新的问题。

压测的场景包括:

  • 正常负载:模拟平时的流量,测延迟和吞吐量。
  • 高峰负载:模拟促销活动的流量,测系统的极限处理能力。
  • 异常场景:模拟后端服务慢或者挂掉,测限流和熔断是否生效。

压测工具我们用的是wrk和Gatling,wrk适合简单的HTTP压测,Gatling适合复杂的场景和报告。

调优全部完成之后,我们做了一次完整的压测,结果是:

  • P50延迟:从50毫秒降到了30毫秒。
  • P95延迟:从300毫秒降到了80毫秒。
  • P99延迟:从800毫秒降到了150毫秒。
  • 吞吐量:从每秒500请求提升到了每秒1500请求。
  • 错误率:从5%降到了0.1%以下。

同时,我们把所有的监控指标接入了告警系统,当延迟、错误率、资源使用率超过阈值的时候,自动告警。这样能及时发现和处理问题,而不是等用户投诉了才知道。

调优的经验和教训

这次网关性能调优,我们总结了一些经验和教训。

第一,先测量再优化。不要凭感觉去调,要用数据说话。先建立监控和基准测试,找出真正的瓶颈,再针对性地优化。很多时候,你以为的瓶颈并不是真正的瓶颈。

第二,调优是一个系统工程。网关的性能涉及连接池、JVM、路由、Filter、限流、操作系统等多个方面,需要全面考虑,不能只盯着一个地方。

第三,注意P99延迟。平均延迟看起来不错,但P99延迟可能很高。用户体验是由最慢的请求决定的,所以要重点关注P99和P999延迟。

第四,不要过度调优。调优要考虑投入产出比,有些优化能带来很大的提升,有些优化只能提升一点点但实现很复杂。优先做投入产出比高的优化。

第五,稳定性比极致性能更重要。一个稳定的、延迟可预测的网关,比一个偶尔很快但经常超时的网关要好。限流、熔断、超时这些机制,和性能调优一样重要。

第六,持续监控和迭代。性能调优不是一次性的工作。流量在变,业务在变,系统在变,需要持续监控,定期做性能测试,不断优化。

写在最后

API网关是微服务架构的咽喉,它的性能直接影响整个系统的响应速度和稳定性。

这次调优,我们通过连接池调优、JVM调优、路由Filter优化、限流熔断、操作系统调优等多个方面的努力,把网关的P99延迟降低了80%,吞吐量提升了3倍。

性能调优没有银弹,需要根据自己的系统特点和瓶颈,针对性地进行优化。但核心思路是一样的:建立监控、找出瓶颈、针对性优化、压测验证、持续迭代。

希望这篇文章能给正在做网关性能调优的朋友一些参考。如果你有更好的调优经验,欢迎交流讨论。