一、引言
在微服务架构中,API 网关作为系统的入口,承担着流量管理、安全控制等重要职责。随着业务的发展,系统面临的并发请求越来越多,为了防止系统因过载而崩溃,限流成为了必不可少的手段。本文将详细介绍基于 IP、接口与用户维度的多级别限流配置,帮助大家更好地应对高并发场景。
二、应用场景
2.1 防止恶意攻击
在互联网环境中,恶意攻击者可能会通过大量的请求来耗尽系统资源,导致正常用户无法访问服务。通过基于 IP 维度的限流,可以限制单个 IP 的请求频率,有效抵御 DDoS 等攻击。例如,某电商网站在促销活动期间,可能会遭受大量恶意 IP 的请求,通过设置 IP 限流,将每个 IP 的请求频率限制在每秒 10 次,就可以防止恶意攻击对系统造成影响。
2.2 保护核心接口
系统中的某些核心接口可能对资源消耗较大,或者处理时间较长。为了保证这些接口的稳定性,需要对其进行限流。比如,一个金融系统中的交易接口,由于涉及到资金的安全和交易的一致性,需要对其请求频率进行严格控制。可以设置该接口的限流规则为每分钟 100 次,确保系统不会因为过多的请求而出现故障。
2.3 公平分配资源
在多用户环境下,为了保证每个用户都能获得合理的资源,需要对用户进行限流。例如,一个云存储服务,为了避免某些用户过度占用存储空间和带宽,会为每个用户设置一定的请求配额。当用户的请求超过配额时,系统会对其进行限流,从而保证其他用户的正常使用。
三、技术优缺点
3.1 优点
3.1.1 提高系统稳定性
通过限流,可以避免系统因过载而崩溃,保证系统在高并发场景下的稳定性。例如,在一个微服务架构的电商系统中,当遇到“双十一”等大促活动时,系统会面临巨大的流量压力。通过合理的限流配置,可以确保系统不会因为瞬间的高并发请求而出现故障,保证用户能够正常下单、支付等。
3.1.2 保护资源
限流可以有效地保护系统的资源,如 CPU、内存、数据库连接等。例如,在一个数据库系统中,如果没有限流机制,大量的并发请求可能会导致数据库连接池耗尽,从而影响系统的正常运行。通过对数据库接口进行限流,可以控制请求的频率,避免资源的过度消耗。
3.1.3 提升用户体验
合理的限流可以保证每个用户都能获得稳定的服务,避免因个别用户的过度请求而影响其他用户的体验。例如,在一个在线游戏中,如果没有限流机制,一些玩家可能会通过脚本等方式进行大量的请求,导致服务器卡顿,影响其他玩家的游戏体验。通过对玩家的请求进行限流,可以保证游戏的流畅性,提升用户的满意度。
3.2 缺点
3.2.1 可能影响正常业务
如果限流规则设置不合理,可能会对正常的业务产生影响。例如,在一个新闻网站中,如果对文章详情页的访问频率限制过低,可能会导致正常用户无法及时获取新闻内容,影响用户体验。
3.2.2 增加系统复杂度
实现多级别限流需要对系统进行一定的改造和配置,增加了系统的复杂度。例如,需要在 API 网关中添加限流模块,并且需要对不同的维度(IP、接口、用户)进行规则配置和管理,这对开发和运维人员的技术水平要求较高。
四、多级别限流配置实现
4.1 基于 IP 维度的限流
我们以 OpenResty 为例来实现基于 IP 维度的限流。OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,它可以方便地实现限流功能。
-- 导入 Redis 模块
local redis = require "resty.redis"
local red = redis:new()
-- 连接 Redis
red:set_timeout(1000) -- 1 秒超时
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 获取客户端 IP
local client_ip = ngx.var.remote_addr
-- 设置限流规则,每秒允许 10 次请求
local limit = 10
local key = "ip_limit:" .. client_ip
-- 增加请求计数
local count, err = red:incr(key)
if not count then
ngx.log(ngx.ERR, "failed to incr Redis key: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 如果是第一次请求,设置过期时间为 1 秒
if count == 1 then
red:expire(key, 1)
end
-- 判断是否超过限流阈值
if count > limit then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.say("Too many requests from your IP.")
return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
-- 释放 Redis 连接
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.log(ngx.ERR, "failed to set keepalive: ", err)
end
上述代码的注释解释如下:
- 首先导入 Redis 模块,并创建一个 Redis 连接对象。
- 然后连接到 Redis 服务器,如果连接失败则记录错误日志并返回 500 错误。
- 获取客户端的 IP 地址,并生成对应的 Redis 键。
- 使用
incr方法增加该 IP 的请求计数,如果是第一次请求则设置过期时间为 1 秒。 - 判断请求计数是否超过限流阈值,如果超过则返回 429 错误。
- 最后释放 Redis 连接。
4.2 基于接口维度的限流
同样使用 OpenResty 实现基于接口维度的限流。
-- 导入 Redis 模块
local redis = require "resty.redis"
local red = redis:new()
-- 连接 Redis
red:set_timeout(1000) -- 1 秒超时
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 获取请求的接口路径
local uri = ngx.var.uri
-- 设置限流规则,每分钟允许 100 次请求
local limit = 100
local key = "uri_limit:" .. uri
-- 增加请求计数
local count, err = red:incr(key)
if not count then
ngx.log(ngx.ERR, "failed to incr Redis key: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 如果是第一次请求,设置过期时间为 60 秒
if count == 1 then
red:expire(key, 60)
end
-- 判断是否超过限流阈值
if count > limit then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.say("Too many requests to this API.")
return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
-- 释放 Redis 连接
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.log(ngx.ERR, "failed to set keepalive: ", err)
end
上述代码的注释解释如下:
- 导入 Redis 模块并创建 Redis 连接对象,连接到 Redis 服务器。
- 获取请求的接口路径,并生成对应的 Redis 键。
- 使用
incr方法增加该接口的请求计数,如果是第一次请求则设置过期时间为 60 秒。 - 判断请求计数是否超过限流阈值,如果超过则返回 429 错误。
- 最后释放 Redis 连接。
4.3 基于用户维度的限流
假设用户通过 JWT 令牌进行身份验证,我们可以从令牌中获取用户 ID,然后实现基于用户维度的限流。
-- 导入 Redis 模块
local redis = require "resty.redis"
local red = redis:new()
-- 连接 Redis
red:set_timeout(1000) -- 1 秒超时
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 获取 JWT 令牌
local auth_header = ngx.var.http_authorization
if not auth_header then
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.say("Authorization header is missing.")
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
-- 解析 JWT 令牌,获取用户 ID
-- 这里假设使用第三方库进行 JWT 解析
local jwt = require "resty.jwt"
local token = string.gsub(auth_header, "Bearer ", "")
local jwt_obj = jwt:verify("your_secret_key", token)
if not jwt_obj.verified then
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.say("Invalid JWT token.")
return ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
local user_id = jwt_obj.payload.user_id
-- 设置限流规则,每小时允许 500 次请求
local limit = 500
local key = "user_limit:" .. user_id
-- 增加请求计数
local count, err = red:incr(key)
if not count then
ngx.log(ngx.ERR, "failed to incr Redis key: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 如果是第一次请求,设置过期时间为 3600 秒
if count == 1 then
red:expire(key, 3600)
end
-- 判断是否超过限流阈值
if count > limit then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.say("Too many requests from your account.")
return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
-- 释放 Redis 连接
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.log(ngx.ERR, "failed to set keepalive: ", err)
end
上述代码的注释解释如下:
- 导入 Redis 模块并连接到 Redis 服务器。
- 获取请求头中的 JWT 令牌,如果令牌缺失则返回 401 错误。
- 使用第三方库解析 JWT 令牌,获取用户 ID,如果令牌无效则返回 401 错误。
- 生成对应的 Redis 键,并使用
incr方法增加该用户的请求计数,如果是第一次请求则设置过期时间为 3600 秒。 - 判断请求计数是否超过限流阈值,如果超过则返回 429 错误。
- 最后释放 Redis 连接。
五、注意事项
5.1 规则配置的合理性
在设置限流规则时,需要根据系统的实际情况进行合理配置。如果限流阈值设置过低,会影响正常业务的运行;如果设置过高,则无法达到限流的效果。例如,在一个低并发的系统中,将接口的限流阈值设置得过高,可能会导致系统在遇到突发流量时无法承受。
5.2 分布式环境下的一致性
在分布式系统中,由于多个 API 网关实例可能同时处理请求,需要保证限流数据的一致性。可以使用 Redis 等分布式缓存来存储限流数据,确保各个实例之间的数据同步。例如,在一个微服务架构中,多个 API 网关实例都需要对 IP 进行限流,通过使用 Redis 作为共享存储,可以保证每个实例获取到的 IP 请求计数是一致的。
5.3 监控和调整
需要对限流情况进行实时监控,根据监控数据及时调整限流规则。例如,通过监控系统可以查看各个维度的限流情况,如 IP、接口、用户的请求频率和限流次数等。如果发现某个接口的限流次数过多,可能需要适当提高该接口的限流阈值。
六、文章总结
本文详细介绍了微服务 API 网关的多级别限流配置,包括基于 IP、接口与用户维度的限流实现。通过合理的限流配置,可以提高系统的稳定性,保护系统资源,提升用户体验。同时,我们也分析了限流技术的优缺点和注意事项。在实际应用中,需要根据系统的具体情况选择合适的限流策略,并进行合理的规则配置和监控调整,以确保系统在高并发场景下能够稳定运行。
评论