限流之Nginx实现接口限流限流

一个Http请求,我们可以在nginx中获取到哪些数据呢?

  1. 请求头部信息:

    • 客户端 IP 地址:通常通过 $remote_addr$binary_remote_addr 变量获取。
    • 请求方法:通过 $request_method 变量获取。
    • URI:通过 $request_uri$uri 变量获取。
    • 主机名:通过 $host 变量获取。
    • 用户代理:通过 $http_user_agent 变量获取。
    • 引用地址(Referer):通过 $http_referer 变量获取。
    • 客户端接受的内容类型:通过 $http_accept 变量获取。
    • 请求的字符编码:通过 $http_accept_charset 变量获取。
    • 请求的语言:通过 $http_accept_language 变量获取。
  2. 请求体信息:

    • 请求的正文内容:通常在应用程序处理请求时访问,或通过 $request_body 变量获取。
  3. Cookie:

    • 客户端发送的 Cookie 数据:通过 $http_cookie 变量获取。
  4. HTTPS 相关信息:

    • 客户端 SSL 证书信息:通过 $ssl_client_cert 变量获取。
    • 客户端 SSL 协议版本:通过 $ssl_protocol 变量获取。
  5. 服务端相关信息:

    • 服务器名称:通过 $server_name 变量获取。
    • 请求方案(HTTP 或 HTTPS):通过 $scheme 变量获取。
    • 服务器监听端口:通过 $server_port 变量获取。
  6. 其他信息:

    • 客户端连接信息,如连接的远程端口、本地端口等。

这些信息在 Nginx 配置中可以使用相应的变量来获取,你可以根据需要在 Nginx 配置中使用这些变量进行请求处理、日志记录、限流等操作。需要注意的是,Nginx 可以通过模块扩展来提供更多的信息和功能,例如限流、缓存、负载均衡等。根据具体的需求,你可以自定义 Nginx 配置以获得所需的信息和功能。

为什么要使用nginx来实现限流?

  1. 保护服务器稳定性:限流可以确保服务器在高流量负载下仍然能够稳定运行,防止过多的请求导致性能下降、服务不可用或服务器宕机。
  2. 公平资源分配:通过限流,Nginx可以确保每个客户端或用户都有公平的机会访问服务器资源,防止某些客户端垄断资源。
  3. 防止恶意请求:限流帮助防止恶意请求,如恶意扫描、爆破攻击、DDoS 攻击等。通过控制请求速率,可以降低对服务器的恶意请求压力。
  4. 合规性:某些服务提供商或服务协议可能要求限制客户端对服务的访问速率。使用Nginx的限流功能可以确保遵守这些规定。
  5. 节省带宽和成本:限流可以减少不必要的网络流量,从而节省带宽成本,特别是在云计算环境中,可以降低使用量和费用。
  6. 避免雪崩效应:在分布式系统中,当一个服务出现故障时,可能会导致大量请求涌入其他服务,造成雪崩效应。通过接口限流,可以减缓请求的涌入,分散负载,避免雪崩。
  7. 控制资源消耗:某些接口可能需要耗费大量服务器资源,如数据库查询或复杂计算。限流可以帮助控制资源消耗,确保服务器资源不被耗尽。
  8. 优化用户体验:通过控制请求速率,可以确保服务器能够及时响应合理数量的请求,提供更快的响应时间,从而改善用户体验。

总之,使用Nginx的限流功能是维护服务器的稳定性、安全性和可用性的重要手段,有助于防止服务器过载、提高系统的稳定性,并确保公平的资源分配。这对于高流量和高负载的应用程序特别有帮助。

Nginx 是如何实现接口限流的?

限制请求速率

Nginx的限流模块是通过ngx_http_limit_req_module实现的,它允许你控制客户端请求的速率,以避免服务器受到过多请求的影响。这个模块的工作原理如下:

  1. limit_req_zone配置 :首先,你需要在Nginx的配置文件中定义一个limit_req_zone来创建一个存储区,用于跟踪每个客户端的请求速率。这个存储区包括了客户端的IP地址、请求计数、时间戳等信息。你可以定义多个不同的存储区来针对不同的请求路径或限流速率。
java 复制代码
http {
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    # ...
}
  • $binary_remote_addr:用于限流的客户端IP地址。
  • zone=api_limit:10m:指定限流存储区的名称和大小(例如10MB)。
  • rate=10r/s:定义速率,例如每秒10个请求。
  1. limit_req配置 :在Nginx配置文件中,你可以在具体的locationserver块内使用limit_req指令来应用限流规则。
java 复制代码
server {
    location /api/ {
        limit_req zone=api_limit burst=15;
        # ...
    }
}
  • zone=api_limit:指定要使用的限流存储区的名称,与之前配置的一致。
  • burst=15:定义允许的突发请求量缓冲区,即瞬时有大量的请求打进来,超过访问限制的请求可以先放到这个缓冲区中。如果也超过了burst的限制,那就需要配合nodelay处理了,如果nodelay on 那就会直接返回503,如果是nodelay off那就将请求放到队列中慢慢消费,但是这个队列大小是不受控制的。所以off设置需要谨慎使用。
  1. 请求处理 :当一个请求到达Nginx,limit_req模块会检查客户端的IP地址和请求速率,以决定是否允许请求继续处理。如果请求速率低于指定的速率,请求会被立即处理。如果请求速率高于指定的速率,请求可能会被延迟处理,以使速率控制生效。

  2. 控制响应 :根据限流规则,limit_req模块会控制请求的处理。如果请求未被限制,它将正常响应。如果请求速率过高,请求可能会被延迟或被返回429 Too Many Requests状态码,表示请求过多。这可以帮助保护服务器免受过载攻击。

    • 请求被延迟 :当请求速率接近或超过了配置的速率限制(rate参数),但未达到或超过瞬时突发请求数(burst参数)时,Nginx会将请求延迟处理。这意味着请求不会被立即响应,而是被推迟一段时间后再处理,以使请求速率不超过限制。
    • 返回429状态码 :当请求速率超过了瞬时突发请求数(burst参数),Nginx会返回HTTP状态码429 Too Many Requests。这表示客户端发送的请求过多,服务器无法及时处理,建议客户端减少请求速率。

通过配置limit_reqlimit_req_zone,Nginx的限流模块能够有效控制请求速率,提高服务器的稳定性和安全性。这对于应对突发请求或恶意攻击非常有用。

rate 参数和burst参数含义:

  1. rate参数rate参数定义了每秒允许的请求速率。如果请求速率高于这个值,请求可能会被延迟或返回429状态码。
  2. burst参数burst参数定义了允许的瞬时突发请求数。如果客户端发送的请求在瞬间突然增加,只有前面的请求达到burst数量会被立即处理,其余的请求会被延迟或返回429状态码。

这里要区分两个时间概念: 每秒瞬时,假如每秒可以处理5个请求,是时间均匀的,但是如果在1秒内的某个瞬间同时来了5个请求,

限制并发连接数

在上面的示例中 $binary_remote_addr 只能是配置为单个ip地址的请求速率,但是我们在实际应用场景中一般是不针对单个ip进行限流的。我们一般只是为URL(接口)提供一个总的限流。那么应该如何配置呢?

我们使用'limit_conn_zone'来实现,以下是代码示例:

java 复制代码
http {
    limit_conn_zone $server_name zone=global:10m;
    
    server {
        location /api/ {
            limit_conn global 10;
            # ...
        }

        # ...
    }
}

在这个示例中,我们使用'limit_conn_zone'来定义了一个全局的限流存储区,但不再使用'$binary_remote_addr' 参数。然后,在'limit_conn'指令中,我们引用了名为'global'的限流存储区,以设置全局的并发连接数限制。

这种配置方式将不会对不同的客户端IP地址进行区分,而是全局控制服务器上的并发连接数。

限制并发连接数&请求速率控制

从上面的两个示例中我们可以看出来,其实'limit_conn_zone '和'limit_req_zone'是两个不同的限流措施。

limit_conn_zone :是ngx_http_limit_conn_module模块的配置指令,用于控制并发连接数的限制。它创建一个共享内存区域,用于跟踪客户端连接数,并设置连接数的限制。这个指令通常用于全局或特定位置来控制并发连接数。

limit_req_zone :是ngx_http_limit_req_module模块的配置指令,用于配置请求速率的限制,而不是并发连接数。

因此,这两个是没有关系的,它们用于不同的控制策略。你可以同时使用这两个模块,根据需求配置并发连接数限制和请求速率限制,以实现全面的流量控制。如果只需要限制并发连接数,你只需要配置 limit_conn_zonelimit_conn 。如果需要同时限制请求速率,可以使用 limit_req_zonelimit_req

限制并发连接数和请求速率的含义

  1. 限制并发连接数(limit_conn):

    • 含义: 限制并发连接数是指限制允许同时建立连接到服务器的客户端数量。它控制的是同时建立连接的数量,而不考虑每个连接的请求频率。
    • 应用场景: 这种策略通常用于保护服务器免受来自某些客户端的滥用或过多的连接请求,防止服务器过载。
    • 例子: 如果设置并发连接数限制为10,意味着每时刻最多只能有10个客户端同时与服务器建立连接,无论这些连接是否同时发起请求。
  2. 控制请求速率(limit_req):

    • 含义: 控制请求速率是指限制客户端对服务器发起请求的频率。它控制的是请求的频率,而不关心同时建立多少连接。
    • 应用场景: 这种策略用于控制每个客户端或每个 IP 地址对服务器的请求频率,以防止过多的请求或滥用服务器资源。
    • 例子: 如果设置请求速率限制为每秒5个请求,意味着每个客户端或 IP 地址每秒最多只能发起5个请求,但他们可以建立多个连接。

总之,限制并发连接数关注的是连接的数量,而控制请求速率关注的是请求的频率。这两种策略可以根据具体需求和应用场景来选择使用,以维护服务器的稳定性和可用性。

limit_req_zone 配置中的属性值详解

  1. key:定义存储区的键,通常用于区分不同的限流存储区。这个属性是必填的。

  2. zone:定义存储区的名称,用于引用该存储区的限流规则。这个属性是必填的。

  3. size :指定存储区的大小。它定义了存储区可以包含的键的数量。这个属性是必填的。大小可以使用以下单位表示:m(兆字节)和 g(千兆字节),例如 10m 表示10兆字节。

  4. rate :定义请求速率限制,表示每秒允许的请求数。这个属性是可选的。如果不定义 rate,则不会对请求速率进行限制,存储区仅用于跟踪请求。

  5. nodelay :可选参数,如果设置为 nodelay,则在存储区用于限制速率时不会有延迟。这个属性是可选的。

  6. status :可选参数,用于设置存储区的状态,可以是 onoff。如果设置为 off,则存储区将被禁用,不会生效。这个属性是可选的。

nodelay 参数详解

nodelay 参数通常用于 limit_reqlimit_conn 指令,以影响限流策略。nodelay 可以设置为以下两个值之一:

  1. nodelay :如果将 nodelay 设置为 nodelay,表示在达到请求速率限制或并发连接数限制时,请求不会被延迟,而是会立即返回 503 (Service Unavailable) 状态码或执行相应的处理。这意味着请求将不会排队等待,而会立即被处理或拒绝。
  2. delay :如果不设置 nodelay 或将其设置为 delay,则请求会被延迟,直到请求速率或并发连接数低于限制为止。这允许请求排队等待,以便在限制条件下逐渐释放请求,而不会立即返回 503。这样可以平滑处理请求,而不会对客户端造成过大的负担。

在流量突增的情况下使用delay有哪些优点?

在突发请求量增加的情况下,如果不使用延迟(即立即返回 503),会导致以下问题:

  1. 请求丢失: 立即返回 503 会导致请求被拒绝,从客户端的角度看,它们的请求可能会被直接丢弃,客户端无法得到响应。
  2. 客户端重新请求: 由于请求被拒绝,客户端可能会尝试重新发送相同的请求,导致更多的请求压力。
  3. 服务质量下降: 突发的大量请求可能会导致服务器过载,影响正常请求的响应时间和服务质量。

当将 nodelay 设置为 delay 时,请求不会立即返回 503,而是被排队等待。这样可以实现以下好处:

  1. 请求排队: 当请求超过限制时,它们会排队等待处理,而不是被拒绝。这有助于平滑处理突发流量。
  2. 限制突发流量: 请求排队可以防止突发请求量对服务器造成过大的冲击,允许服务器逐渐释放请求,以维持较稳定的负载。
  3. 减少请求丢失: 由于请求不会立即丢失,客户端不太可能重新发送相同的请求。
  4. 更好的用户体验: 客户端不会立即遇到拒绝,这有助于提供更好的用户体验,即使在限制条件下。

总之,使用 delay 选项可以更好地处理突发请求,并避免对客户端造成过大的负担,从而提高系统的可用性和稳定性。但需要根据具体需求来选择使用 nodelay 还是 delay

limit_req_zone中的key都有哪些?(上面示例的key为**$binary_remote_addr**)

  • $binary_remote_addr: 可以根据IP地址限制请求速率。
  • $server_name:可以根据服务器名限制请求速率。
  • $host:这是客户端请求的主机名,也用于根据不同的主机名进行限流。
  • $uri:可以根据URI(请求的路径)限制请求速率。
  • $http_user_agent:可以根据用户代理限制请求速率,以区分不同的浏览器或客户端。
  • 自定义变量:你还可以定义和使用自定义变量来灵活控制请求速率。

limit_conn_zone中的key都有哪些?

  1. $binary_remote_addr:客户端的二进制形式的 IP 地址。
  2. $remote_addr:客户端的 IP 地址(通常以字符串形式)。
  3. $http_header_name:HTTP 请求中的任何标头的名称。例如,$http_user_agent 表示用户代理标头。
  4. $http_name:HTTP 请求中的任何标头的值。例如,$http_referer 表示引用标头的值。
  5. $server_name:Nginx 服务器名。
  6. $host:HTTP 请求中的主机标头值。
  7. $arg_name:HTTP 请求中的查询参数的名称。例如,$arg_id 表示查询参数 "id" 的值。
  8. $cookie_name:HTTP 请求中的 cookie 的名称。例如,$cookie_session 表示名为 "session" 的 cookie 的值。
  9. $remote_user:HTTP 基本身份认证的用户名。
  10. $request_method:HTTP 请求方法(如 GET、POST 等)。
  11. $uri:HTTP 请求的 URI。
  12. $request_uri:完整的 HTTP 请求 URI,包括查询参数。
  13. $request_filename:Nginx 请求的文件名(绝对路径)。

Nginx的限流模块底层是如何实现的?

ngx_http_limit_req_module 模块是 Nginx 的 HTTP 请求限流模块,底层是通过 Nginx 的事件驱动架构实现的。模块使用了令牌桶算法来控制客户端的请求速率。

ngx_http_limit_req_module 模块的底层实现依赖于 Nginx 的事件驱动架构,包括事件循环、定时器机制以及连接池等。这确保了请求速率得到有效的限制和控制,而不会阻塞其他请求的处理。

配置示例:

场景: your_api_endpoint接口支持的QPS为800,并且TP99在可以接受的范围之内,使用Nginx实现接口限流,并发控制和请求速率共用。

java 复制代码
http {
    limit_conn_zone $binary_remote_addr zone=concurrent:10m;
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=5r/s;

    server {
        listen 80;
        server_name your_server_name;

        location /your_api_endpoint {
            limit_conn concurrent 10;  # 限制每个IP的并发连接数
            limit_req zone=req_limit burst=10 nodelay;  # 限制请求速率

            # 设置整体IP地址数量的限制
            limit_conn conn_limit 100;

            # 允许的请求处理逻辑
            proxy_pass http://backend_server;
        }
    }

    # 其他服务器块和配置...
}

Nginx 限流可能会出现的问题

  1. 突发请求处理 :默认情况下,limit_req模块会设置一个允许的突发请求量(burst),当超过这个量时,请求会被限制。你需要根据应用程序的需求来合理设置突发请求量。如果设置得太低,可能会影响应用的性能;如果设置得太高,可能无法有效限制请求速率。

  2. 客户端IP地址共享limit_req模块以客户端IP地址为基础进行限流。如果多个客户端共享相同的公共IP地址(例如,位于同一家公司或使用代理服务器),那么限流规则将适用于整个IP地址范围,而不是个别客户端。这可能导致一些合法请求受到不必要的限制。

  3. 配置调整:配置限流规则需要谨慎,如果设置不当,可能会导致应用程序不可用或无法响应真实流量。因此,需要根据应用的实际情况和预期的负载来进行适当的调整。

  4. DDoS 攻击 :虽然limit_req模块可以帮助减轻恶意请求的影响,但仍然需要注意可能的分布式拒绝服务(DDoS)攻击。针对大规模恶意请求的攻击需要其他防御措施,如CDN、防火墙等。

  5. 日志记录:当请求被限制时,Nginx可能会记录相关的错误消息,这可能会导致日志文件变得庞大。需要谨慎处理和维护日志,以防止磁盘空间耗尽。

  6. Nginx 版本兼容性 :不同版本的Nginx可能会有不同的模块行为和配置选项。确保你的Nginx版本支持ngx_http_limit_req_module模块,并仔细查看相应版本的文档。

总之,使用ngx_http_limit_req_module模块是一种有力的控制请求速率的方法,但需要仔细配置和监控,以确保它满足应用程序的需求,并保护服务器免受恶意请求的影响。在实施时,需要综合考虑应用的性能和安全需求。

按照上面的两种方式限制,都不能按照接口实现整体的QPS限流,如果想要单纯的实现接口QPS限流,可以使用Lua脚本来实现

使用Lua脚本来实现接口整体QPS限流

要在 Nginx 中实现整体接口的 QPS 限制为 800,您可以使用 Nginx 的 ngx_http_limit_req_module 模块结合 Lua 脚本来实现。以下是一个示例配置:

首先,确保您已经启用了 ngx_http_limit_req_module 模块,可以在 Nginx 配置中使用 load_module 来加载它。

java 复制代码
load_module modules/ngx_http_limit_req_module.so;

然后,创建一个 Lua 脚本,以便在 Nginx 配置中使用它来实现整体 QPS 限制。以下是一个示例的 Lua 脚本 (qps_limit.lua),该脚本定义了限制策略:

lua 复制代码
-- qps_limit.lua

-- 共享内存区域,用于存储请求速率限制状态
local shared = ngx.shared.qps_limit

-- 请求速率限制配置
local limit = 800  -- 整体的 QPS 限制
local burst = 0  -- 突发请求允许的数量

-- 获取当前时间戳
local now = ngx.now()

-- 获取当前请求的客户端IP地址
local client_ip = ngx.var.remote_addr

-- 获取客户端IP地址的请求计数,如果不存在则初始化为0
local client_count = shared:get(client_ip) or 0

-- 计算请求速率
local rate = client_count / (now - shared:get("last_time"))

-- 如果请求速率超过限制,返回请求拒绝
if rate > limit then
    ngx.status = 503
    ngx.say("Request limit exceeded")
    ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
else
    -- 增加客户端IP地址的请求计数
    shared:incr(client_ip, 1)

    -- 更新最后的计数时间
    shared:set("last_time", now)
end

接下来,将 Lua 脚本包含到 Nginx 配置中,并在配置中定义一个 access_by_lua 块来调用脚本,实现整体 QPS 限制。以下是示例配置:

nginx 复制代码
http {
    lua_shared_dict qps_limit 10m;  # 共享内存区域

    server {
        listen 80;
        server_name your_server_name;

        location /your_api_endpoint {
            access_by_lua_file /path/to/qps_limit.lua;  # 调用 Lua 脚本

            # 允许的请求处理逻辑
            proxy_pass http://backend_server;
        }
    }

    # 其他服务器块和配置...
}

在这个配置中,我们使用 lua_shared_dict 定义了一个共享内存区域,用于存储请求速率限制的状态信息。然后,在 /your_api_endpoint 的位置块中,我们使用 access_by_lua_file 调用 Lua 脚本,该脚本实现了整体 QPS 限制策略。如果请求速率超过限制,将返回 503 响应,否则会继续处理请求。

使用Lua实现接口限流存在的问题:

  1. 性能开销: 使用 Lua 脚本会增加 Nginx 的处理负担,特别是在高流量环境下。对于高 QPS 的应用,性能开销可能会显著。
  2. 复杂性: Lua 脚本的编写和维护需要一定的专业知识,对于不熟悉 Lua 的管理员来说可能会复杂一些。
  3. 难以扩展: 如果未来需要实现更复杂的限流策略或与其他组件进行集成,Lua 脚本可能不够灵活。
  4. 不支持动态配置: 使用 Lua 脚本的限流策略通常需要硬编码在配置文件中,不够灵活,无法根据动态需求进行调整。

但是我们的应用场景,使用Lua就可以满足,所以就没有选择其他的方案。

更好的解决方案

  1. 内置模块: Nginx 提供了一些内置模块,如 ngx_http_limit_req_modulengx_http_limit_conn_module,可以用来实现请求速率和并发连接的限制。这些模块是高性能的,无需使用 Lua 脚本。
  2. OpenResty: 如果您需要更高级的限流策略,可以考虑使用 OpenResty,它是一个基于 Nginx 的扩展,支持 Lua 编程,并提供了丰富的功能和性能。
  3. Nginx Plus: 如果您使用的是 Nginx Plus,它提供了高级的限流功能,包括 Dynamic Modules 和 Key-Value Store 模块,可用于更复杂的限流策略。
  4. CDN 和负载均衡器: 在一些场景下,CDN(内容分发网络)和负载均衡器可以用来分散流量和实施一定程度的限流。

我们还使用了内置模块来实现对文件下载接口的限流。

相关推荐
AskHarries7 分钟前
Java字节码增强库ByteBuddy
java·后端
佳佳_21 分钟前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
马剑威(威哥爱编程)30 分钟前
MongoDB面试专题33道解析
数据库·mongodb·面试
许野平2 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
独行soc3 小时前
#渗透测试#SRC漏洞挖掘#深入挖掘XSS漏洞02之测试流程
web安全·面试·渗透测试·xss·漏洞挖掘·1024程序员节
理想不理想v3 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
齐 飞3 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod3 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
sszmvb12344 小时前
测试开发 | 电商业务性能测试: Jmeter 参数化功能实现注册登录的数据驱动
jmeter·面试·职场和发展
码农派大星。4 小时前
Spring Boot 配置文件
java·spring boot·后端