一、为什么调这个:API调用被429拦截的真实场景
我之前参与过一个电商平台的项目,负责商品查询API的稳定性保障。上线后没几天,运营反馈说大促预热的时候,很多用户打不开商品详情页,前端报429错误。我们排查下来,是之前没给API设限流策略,用户批量刷商品接口,导致后端数据库被打挂,Azure API Management(以下简称APIM)作为入口网关,为了保护后端,就把超过流量限制的请求直接返回了429状态码,也就是用户常看到的“请求太频繁,请稍后再试”的错误。那时候客户端根本没处理这个429,直接报错给用户,体验特别差。
1.1 最初的错误配置坑
一开始我们为了省事,只给单节点的APIM实例设了限流,总限制是每分钟100次。但APIM是集群部署的,有多个节点,每个节点自己算限流,结果总实际能处理的流量是(节点数×100),远超过我们预期的总流量,导致总请求量到了每分钟500次的时候,APIM没有触发限流,后端被压垮了。后来才发现,原来本地限流和全局限流的区别这么大。
二、先搞懂基础:APIM里的限流策略
要解决429问题,得先搞懂APIM里的限流分两种:本地限流和全局限流。
2.1 本地vs全局限流的本质
本地限流,就是每个APIM的节点自己算调用次数,比如每个节点允许每分钟100次,集群有5个节点的话,总就能处理500次,这种方式简单,但没法统一控制总流量,容易出现刚才的后端被压垮的情况。而全局限流,是整个APIM集群共用一个流量计数器,不管多少节点,总调用数都不会超过设置的上限,这才是我们要的保护方式。
2.2 令牌桶是全局限流的核心
全局限流的底层逻辑其实就是令牌桶算法,这个算法可以生活化理解成:想象你要去一个热门景点,门口有个自动发门票的机器(令牌生成器),每分钟会吐出一定数量的门票(令牌),你每次进景点(调用API)需要拿一张门票。如果游客太多,门票发完了,后来的人要么等下一张门票,要么被拦在门外(返回429)。为了应对突发的人流(比如大促的时候突然来很多用户),机器还会存一些备用门票(令牌桶的容量),比如最多存150张,这样突然来100个人都能进去,不会一下子被全部拦住。
三、从产品策略开始优化:先改APIM里的限流规则
我们首先在APIM的管理界面,给商品查询API配置全局的限流策略,一开始的配置是这样的,用APIM的策略代码,我给大家加上注释:
{
"scopes": ["/products/*"], // 这个策略的作用范围是所有商品查询相关的API路径,比如/products/detail、products/list都适用
"policies": [
{
"name": "rate-limit-by-key", // 这里用的是按key限流,key可以自定义,我们选了客户端IP,避免单个恶意IP刷爆接口
"attributes": {
"key": "@(context.Request.IpAddress)", // 把客户端的IP地址作为限流的key,每个IP独立计数
"calls": "100", // 每分钟允许每个IP调用100次
"renewal-period": "60", // 每60秒重置一次计数,也就是每分钟的限制
"bucket-size": "150" // 令牌桶的最大容量,也就是允许的突发流量,比如瞬间来120次请求,只要在60秒内总调用不超过150,就会被允许
}
}
]
}
3.1 这个策略的好处和问题
这个策略改完后,单个IP不会随便刷,总流量也被控制住了,后端服务的压力明显小了,429的返回率从15%降到了5%。但还有问题:大促的时候,有的IP因为同时给多个用户用(比如公司的出口IP),总请求量会超过100次,还是会返回429。这说明只按IP限流还不够,得调整令牌桶的参数,或者换限流的key。
四、深入调优:全局速率令牌桶的参数怎么算
后来我们做了两次调优,解决了刚才的问题。首先,我们把限流的key改成了API客户端的标识(比如请求头里的X-Client-Id,每个客户端对应一个APP ID,不会因为共享IP被限),这样就更准确了。然后调整了令牌桶的容量和补充速率:
{
"scopes": ["/products/*"],
"policies": [
{
"name": "rate-limit-by-key",
"attributes": {
"key": "@(context.Request.Headers.GetValueOrDefault(\"X-Client-Id\", \"default\"))", // 按APP的ID限流,而不是IP,更贴合业务场景
"calls": "120", // 每分钟给每个APP ID补充120个令牌
"bucket-size": "200", // 令牌桶最大能存200个令牌,应对突发流量,比如大促时APP的用户突然都来刷接口
"renewal-period": "60"
}
}
]
}
4.1 调优参数的计算方法
这里的参数不是拍脑袋的,我们是根据业务监控数据来算的:之前大促时,商品接口的峰值是每分钟每个APP ID调用180次,所以把桶的容量设成200,足够应对这种峰值;每分钟补充120个,保证长期来看总流量不会超过120次/分钟,既不会被频繁返回429,又能保护后端服务。另外,APIM要开启全局缓存,这样各个节点的令牌数才会同步,不然还是会出现各算各的问题,这个是之前踩过的坑,必须注意。
五、技术优缺点和注意事项
5.1 限流策略的优缺点
优点:第一,保护后端服务不会被流量压垮,比如大促时数据库不会崩溃;第二,可控性强,我们可以根据不同API的重要性设置不同的限流规则,比如用户登录API可以设高一点,静态资源API设低一点;第三,全局限流的一致性好,不会因为节点分布不均导致的限流混乱。 缺点:如果配置太严格,比如令牌桶容量设太小,正常的突发流量也会被拦,影响用户体验;另外,限流策略的维护需要结合业务数据,没有数据支撑的配置很容易出问题;还有,如果APIM的全局缓存出问题,令牌桶的计数就会不一致,导致限流失效。
5.2 调优的注意事项
首先,一定要有业务监控数据,比如平时的平均请求量、峰值请求量,根据这些来算参数,不能凭感觉;其次,全局限流必须开启APIM的全局缓存,不然各个节点的计数器不同步,会出现总流量超过设置上限的情况;第三,给错误做降级,比如客户端收到429后,不要直接报错,应该等待一段时间后重试,或者返回降级内容,比如“当前访问人数较多,请稍后再试”的提示,优化用户体验;第四,不要给所有API都设一样的限流,比如管理后台的API可以设得高一点,公开的API设得低一点,分场景配置。
六、应用场景总结
这种全局速率令牌桶的调优,适合的场景特别多:第一,电商大促的核心API,比如商品查询、订单查询,用这个策略应对突发流量;第二,内部系统的API,给开发测试用,避免有人刷接口影响其他开发;第三,第三方合作的API,按合作方的APP ID限流,防止恶意调用;第四,高频的用户操作API,比如投票、评论,限制每个用户的调用次数,防止刷数据。
七、调优后的实际效果
我们把刚才的调优策略上线后,再做大促压力测试,商品API的429返回率从之前的5%降到了0.3%,几乎没有正常调用被拦截。后端数据库的CPU使用率从80%降到了30%,稳定了很多。前端也做了优化,收到429后不会直接报错,而是显示“访问太火爆啦,请稍后再试~”的提示,用户体验明显提升,运营的投诉也少了很多。
评论
围绕“Azure API Management限制调用频率后客户端报429,从产品策略到全局速率令牌桶的调优”参与讨论