玩转Nginx之限流-三大限流算法实现
概述:
Nginx 实现限流主要依赖于其内置的模块,如 ngx_http_limit_req_module
和 ngx_http_limit_conn_module
。这些模块提供了基于固定窗口算法和漏桶算法的限流功能。
实现原理
-
固定窗口算法 (
ngx_http_limit_req_module
):- 这个模块允许你定义一个请求速率(比如每秒允许的请求数量)和一个时间窗口。
- 当请求到达时,Nginx 会检查在当前的时间窗口内是否已经达到了设置的速率限制。
- 如果请求超出了速率限制,Nginx 可以配置为返回错误状态码(如
503 Service Temporarily Unavailable
)或者延迟处理请求。
-
漏桶算法 (
ngx_http_limit_conn_module
):- 这个模块限制了同时处理的连接数,可以用来限制对某个资源(如服务器、数据库等)的并发访问数。
- 当达到限制的连接数时,新的连接会被拒绝,直到现有的连接数降低。
3. 令牌桶算法(ngx_http_limit_req_module
):
- 令牌桶算法通过固定速率向桶中添加令牌。每个请求都需要消耗一个令牌才能被处理。如果桶中有足够的令牌,请求将立即被处理;如果没有令牌,则请求可以被延迟处理或拒绝。这种算法允许一定程度的突发流量,因为桶中可以积累令牌以应对短时间内的请求峰值。 -
优点
- 性能高效:Nginx 作为一个高性能的反向代理服务器,其限流功能非常高效,对性能的影响很小。
- 易于配置:Nginx 的配置文件简洁易懂,可以轻松设置限流规则。
- 灵活性:可以对不同的服务器、位置(location)或者服务设置不同的限流规则。
- 稳定性:Nginx 是一个经过广泛测试和使用的软件,具有很高的稳定性。
缺点
- 限流算法简单:Nginx 提供的限流算法相对简单,主要是固定窗口算法,可能会导致临界问题。
- 无法处理分布式环境:在单个 Nginx 实例上限流很简单,但在分布式环境下同步限流状态较为复杂,可能需要额外的工具或模块。
- 功能有限:Nginx 的限流功能相对基础,缺乏一些高级功能,如权重限流、动态限流等。
使用示例
以下是一个简单的 Nginx 限流配置示例:
http {
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
server {
location / {
limit_req zone=mylimit burst=5;
...
}
}
}
在这个例子中,limit_req_zone
指令定义了一个名为 mylimit
的限流区域,使用客户端的 IP 地址作为关键字,并设置了每秒允许的请求速率为 1 次。burst
参数定义了在超过速率限制时允许的突发请求数,这些请求会被延迟处理而不是立即拒绝。
固定窗口算法:
Nginx 中实现基于固定窗口算法的限流,你需要使用 limit_req
模块。以下是一个基本的配置示例,说明了如何在 Nginx 中设置固定窗口算法限流:
http {
# 定义一个限流区域,这里使用客户端的 IP 地址作为限流的键值
# $binary_remote_addr 是客户端 IP 地址的二进制表示,用于节省空间
# zone=mylimit:10m 定义了名为 mylimit 的限流区域,10m 表示为这个区域分配 10MB 内存
# rate=1r/s 表示限制速率为每秒一个请求
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
server {
# ...
location / {
# 应用限流规则到指定的 location
# zone=mylimit 指定使用之前定义的限流区域 mylimit
# burst=5 表示允许的突发请求量为 5
# nodelay 表示不对突发请求进行延迟处理,如果不指定 nodelay,超过 rate 限制的请求将被延迟处理
limit_req zone=mylimit burst=5 nodelay;
# 其他 location 配置 ...
}
# 其他 server 配置 ...
}
}
在这个配置中,我们定义了一个名为 mylimit
的限流区域,并设置了每秒允许的请求速率为 1 次。我们还为这个限流区域分配了 10MB 的内存,足以存储大约 160,000 个 IP 地址的状态信息。
在 server
块中,我们将这个限流规则应用到了根路径 /
的 location
块。我们设置了 burst=5
,这意味着如果请求速率超过了每秒 1 次,Nginx 将允许多达 5 个额外的请求立即被处理,而不是被拒绝或延迟。nodelay
参数的存在意味着即使请求超过了 burst
值,也不会被延迟处理,而是立即返回错误。
如果你希望超过 burst
值的请求被延迟处理,而不是立即返回错误,可以去掉 nodelay
参数:
limit_req zone=mylimit burst=5;
此时,如果请求速率超过了 burst
值,这些请求将会被延迟处理,直到它们可以在没有超过速率限制的情况下被处理。
流程图:
在这个流程图中:
- 客户端 发出请求到 Nginx 服务器。
- Nginx 服务器使用 LimitReqModule(限制请求模块)来检查请求是否超出了设定的速率限制。
- LimitReqModule 根据固定窗口算法判断请求是否在速率限制内:
- 如果请求没有超过速率限制,请求将被处理(Within Rate Limit),然后 Nginx 返回处理结果给客户端。
- 如果请求超过了速率限制,Nginx 将返回 HTTP 状态码 503(Rate Limit Exceeded)。
时序图:
- 客户端 发起请求到 Nginx 服务器。
- Nginx 服务器收到请求后,调用 LimitReqModule(限制请求模块)来检查请求是否符合固定窗口算法的限制条件。
- LimitReqModule 根据当前时间窗口内的请求计数来决定是否允许或拒绝请求:
- 如果请求在速率限制内(即当前时间窗口内的请求计数未超过预设的阈值),LimitReqModule 通知 Nginx 允许请求,然后 Nginx 返回成功响应给客户端。
- 如果请求超出速率限制(即当前时间窗口内的请求计数超过了预设的阈值),LimitReqModule 通知 Nginx 拒绝请求,接着 Nginx 返回 503 错误给客户端。
请注意,为了使这些配置生效,你需要重新加载或重启 Nginx。此外,这些配置只适用于单个 Nginx 实例。在分布式或负载均衡环境中,你可能需要考虑使用共享存储解决方案或其他中间件来同步限流状态。
漏桶算法-limit_conn:
在 Nginx 中实现漏桶算法限流通常是通过 limit_conn
模块完成的。该模块允许限制对指定资源的并发连接数。虽然漏桶算法的原始定义是不同的,Nginx 的 limit_conn
模块实际上实现了一个类似于漏桶的机制,可以看作是漏桶算法的一个变种,用于限制并发连接数而非请求速率。以下是一个基本的 Nginx 配置示例,展示了如何使用 limit_conn
模块来限制并发连接数:
http {
# 定义一个限流区域,这里使用客户端的 IP 地址作为限流的键值
# $binary_remote_addr 是客户端 IP 地址的二进制表示,用于节省空间
# zone=connlimit:10m 定义了名为 connlimit 的限流区域,10m 表示为这个区域分配 10MB 内存
# 这个内存区域将用于跟踪并发连接状态
limit_conn_zone $binary_remote_addr zone=connlimit:10m;
server {
# ...
location / {
# 应用限流规则到指定的 location
# zone=connlimit 指定使用之前定义的限流区域 connlimit
# connections=3 表示每个客户端 IP 允许的最大并发连接数为 3
limit_conn connlimit 3;
# 其他 location 配置 ...
}
# 其他 server 配置 ...
}
}
在这个配置中,我们定义了一个名为 connlimit
的限流区域,并为这个区域分配了 10MB 的内存,足以跟踪大量的并发连接状态。我们将这个限流规则应用到了根路径 /
的 location
块,并设置了每个客户端 IP 地址允许的最大并发连接数为 3。如果超出这个限制,Nginx 将返回 503 Service Temporarily Unavailable
错误。
这种配置可以有效地限制对服务器资源的过度使用,例如防止单个 IP 地址发起太多并发连接,从而对服务器造成过载。这对于控制对静态文件、API 端点或其他需要限制并发访问数的资源尤其有用。
流程图:
流程图中:
- 客户端 发出请求到 Nginx 服务器。
- Nginx 服务器使用 LimitConnModule(限制连接模块)来检查当前并发连接数。
- LimitConnModule 判断当前并发连接数是否超过设定的限制:
- 如果当前并发连接数未超过限制,请求将被处理(Under Limit),然后 Nginx 返回处理结果给客户端。
- 如果当前并发连接数超过限制,请求被模拟排队(Over Limit)。在真实的 Nginx 配置中,这通常意味着请求会被拒绝,但在这个理论模型中,请求会等待处理,或者在超时或达到其他错误条件时返回错误。
时序图:
在这个时序图中:
- 客户端 发起请求到 Nginx 服务器。
- Nginx 服务器收到请求后,调用 LimitConnModule(限制连接模块)来检查当前对特定资源的并发连接数。
- LimitConnModule 根据并发连接数来决定是否允许或队列请求:
-
如果并发连接数未超过限制,请求被允许,Nginx 处理请求并返回成功响应给客户端。
-
如果并发连接数超过限制,请求可能被放入队列。在这种情况下,请求要么在稍后被处理(模拟漏桶的恒定输出),要么在超时或其他条件下返回错误。
-
请注意,为了使这些配置生效,你需要重新加载或重启 Nginx。此外,这些配置只适用于单个 Nginx 实例。在分布式或负载均衡环境中,你可能需要考虑使用共享存储解决方案或其他中间件来同步限流状态。
漏桶算法-limit_req:
Nginx 的 ngx_http_limit_req_module
模块实现的是一种类似于令牌桶算法的速率限制机制,它可以用来控制对HTTP服务器的请求速率以防止过载。尽管这种机制在概念上与漏桶算法有所不同,但它们都用于流量控制,且 ngx_http_limit_req_module
可以模拟漏桶算法的效果。### 原理
令牌桶算法的工作原理是这样的:系统以一个恒定的速率向桶中添加令牌,直到达到桶的容量上限。每个传入的请求都需要从桶中获取一个令牌来被处理。如果桶中没有令牌,请求可以等待直到有令牌可用,或者被立即拒绝。
漏桶算法的工作原理则是:系统以一个恒定的速率处理请求,如果请求到达的速度超过了处理速率,多余的请求会在桶中排队,直到桶满为止。一旦桶满,新到达的请求就会被丢弃。
在 Nginx 的 ngx_http_limit_req_module
中,可以设置一个请求速率和一个突发请求容量(burst),这样允许在限制请求速率的同时处理一定数量的突发请求。
使用示例
以下是一个 Nginx 配置文件的示例,展示了如何使用 ngx_http_limit_req_module
来限制请求速率:
http {
# 定义限制区域
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
server {
listen 80;
server_name example.com;
location / {
# 应用请求速率限制
limit_req zone=mylimit burst=5 nodelay;
# 正常的代理或其他配置
proxy_pass http://my_backend;
}
}
}
在这个配置中:
limit_req_zone
指令定义了一个名为mylimit
的限制区域,使用$binary_remote_addr
(通常是客户端的 IP 地址)作为限制键,以及10m
的共享内存大小。rate=1r/s
表示限制请求速率为每秒 1 个请求。burst=5
参数允许处理短时间内超过速率限制的最多 5 个请求。如果超出这个数量,请求将根据nodelay
选项被处理:- 如果设置了
nodelay
,则超出速率限制的请求将立即被处理,直到达到burst
容量上限。这模拟了漏桶算法中的立即处理而不排队的行为。 - 如果没有设置
nodelay
,则超出速率限制的请求将被延迟处理,直到它们可以在定义的速率下被处理。
- 如果设置了
这个配置可以帮助保护应用程序免受流量峰值的影响,同时还允许一定程度的突发流量。需要注意的是,当请求被限制时,默认的行为是返回一个 503 Service Temporarily Unavailable
错误,但是可以通过配置自定义错误页面来改变这一行为。
在这个流程图中:
- 客户端向 Nginx 发起请求。 - Nginx 检查当前请求是否超出了预设的速率限制。 - 如果请求没有超出速率限制,它将被处理。 - 如果请求超出了速率限制,Nginx 将检查是否有足够的突发容量来处理这个请求。 - 如果有足够的突发容量,请求将被处理。 - 如果没有足够的突发容量,请求将被排队等待处理或直接被拒绝。 - 最后,Nginx 将返回相应的响应给客户端,要么是成功的响应,要么是503错误表示请求被拒绝。
时序图:
在这个时序图中:
- 客户端 向 Nginx 发起请求。
- Nginx 通过 LimitReqModule(限制请求模块)来检查请求是否超出了设定的速率限制。
- 根据请求是否在速率限制之内,LimitReqModule 会做出不同的响应:
- 如果请求速率在限制之内,请求将被允许,Nginx 处理请求并返回成功响应给客户端。
- 如果请求速率超出限制,模块会检查是否有突发(burst)设置和是否启用了延迟(nodelay):
- 如果设置了突发并且请求没有超过突发容量,请求将被允许。
- 如果不允许突发或请求超出了突发容量,请求将被拒绝,Nginx 返回 503 错误给客户端。
滑动窗口算法:
Nginx 本身是不支持滑动窗口算法。滑动窗口算法涉及到持续跟踪和更新每个请求的时间戳,这在 Nginx 的标准模块中并不直接支持。然而,可以使用第三方模块或自定义的 Lua 脚本(通过 Nginx 的 ngx_http_lua_module
模块)来在 Nginx 中模拟滑动窗口算法。ngx_http_lua_module
允许你在 Nginx 配置中嵌入 Lua 代码,可以利用 Lua 来访问和操作共享内存区域,从而实现更复杂的限流算法。
以下是一个使用 Lua 实现滑动窗口��法限流的基本示例:
http {
lua_shared_dict my_limit 10m;
init_by_lua_block {
local limit_req = require "resty.limit.req"
ngx.shared.my_limit = limit_req.new("my_limit", 2, 0.5)
}
server {
location / {
access_by_lua_block {
o limit req: ", err)
return ngx.exit(500)
end
if delay > 0 then
ngx.sleep(delay)
end
}
}
}
}
在这个配置中,我们使用了 lua_shared_dict
指令来创建一个名为 my_limit
的共享内存区域,然后在 init_by_lua_block
中初始化了 resty.limit.req
对象。这个对象用于实现限流逻辑,其中 2
表示每秒允许的请求数,0.5
表示在这个速率下处理一个请求需要的时间(秒)。
在 server
配置块的 location /
中,access_by_lua_block
使用 Lua 代码来检查请求是否应该被限流。如果请求被接受,它将继续正常处理;如果请求被限流,将返回 503 Service Temporarily Unavailable
。
要注意的是,上述配置依赖于 OpenResty 的 lua-resty-limit-traffic
库,OpenResty 是一个基于 Nginx 和 LuaJIT 的 Web 平台,它集成了许多 Lua 库,可以用来实现复杂的 Web 应用逻辑,包括滑动窗口限流。
流程图:
- 客户端 发出请求到 Nginx 服务器。
- Nginx 服务器在接收到请求后,会调用配置好的 Lua 脚本来处理限流逻辑。
- Lua 脚本会检查共享内存区域(Shared Dict),这是一个由
lua_shared_dict
指令创建的共享内存空间,用于存储滑动窗口的状态信息。 - 共享内存返回当前状态给 Lua 脚本,包括当前时间窗口内的请求计数和时间戳。
- Lua 脚本根据共享内存中的信息决定是否接受或拒绝请求:
- 如果请求没有超过限流条件,Lua 脚本将允许请求继续处理(Accept)。
- 如果请求超出了限流条件,Lua 脚本将拒绝请求并返回 HTTP 状态码 503(Reject)。
- 最终,Nginx 服务器会将处理结果返回给 客户端。
时序图:
在这个时序图中:
- 客户端 发起请求到 Nginx 服务器。
- Nginx 服务器收到请求后,调用预先配置的 Lua 脚本 来处理限流逻辑。
- Lua 脚本查询 共享内存(SharedMemory)中关于当前时间窗口的请求统计数据。
- 共享内存返回当前窗口的请求统计信息给 Lua 脚本。
- Lua 脚本根据共享内存中的数据判断请求是否超过了设定的限制。
- 如果请求数未超过限制,Lua 脚本通知 Nginx 允许请求,并且 Nginx 处理并返回请求结果给客户端。
- 如果请求数超过限制,Lua 脚本通知 Nginx 拒绝请求,Nginx 随后返回一个 503 错误给客户端。
由于滑动窗口算法限流在 Nginx 中不是原生支持的,因此这种实现方式可能需要额外的性能考虑,并且在高并发场景下,可能需要对 Lua 脚本和 Nginx 配置进行细致的调优。对于在分布式环境中实现滑动窗口算法限流,可能需要使用 Redis 等外部存储来同步不同 Nginx 实例之间的状态。
令牌桶算法:
原理
Nginx 的 ngx_http_limit_req_module
模块实现的是令牌桶算法(Token Bucket Algorithm),该算法用于控制进入网络系统的数据流量以避免网络拥塞。令牌桶算法的核心概念是令牌桶,它以固定速率填充令牌。每个传入请求都需要消耗一个令牌才能被处理。如果桶中没有足够的令牌,请求可以根据配置被延迟处理或拒绝。
使用示例
下面是一个使用 ngx_http_limit_req_module
模块的 Nginx 配置示例:
http {
# 定义限制请求的速率区域
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
server {
listen 80;
server_name example.com;
location / {
# 应用请求速率限制
limit_req zone=mylimit burst=5 nodelay;
# 以下是你的正常配置,比如代理设置
proxy_pass http://my_backend;
}
}
}
在这个配置中:
-
limit_req_zone
指令定义了一个名为mylimit
的限制区域。$binary_remote_addr
表示基于客户端 IP 地址来跟踪和限制请求速率。10m
是为这个区域分配的内存大小,rate=1r/s
表示每秒允许一个请求通过。 -
limit_req
指令应用于特定的location
块,代表这个位置的请求将受到限制。zone=mylimit
引用了之前定义的限制区域,burst=5
允许在超出正常请求速率时额外累积最多 5 个请求(这相当于桶的容量),而nodelay
表示不对符合突发条件的请求增加任何额外的延迟。
请注意,如果请求超出了 burst
设置的容量,那么这些请求将会收到一个 503 Service Temporarily Unavailable 错误,除非你配置了不同的错误处理。这个配置有助于保护你的应用程序免于流量峰值的影响,同时允许一定程度的突发流量。
该算法和漏桶算法-limit_req 一样,故此不再详细介绍。
总结:
总的来说,Nginx 的限流功能适用于大多数基本需求,特别是当你需要快速、简单地在单个服务器上实现限流时。对于更复杂的限流需求,可能需要考虑使用其他工具或服务。后续将继续讲解使用redis 实现如何如何限流。
转载自:https://juejin.cn/post/7368372944584474665