玖叶教程网

前端编程开发入门

Nginx 面试通关秘籍-3.如何用 Nginx 限流,几种限流算法如何实现?

在当今互联网时代,随着网络应用的普及和用户数量的不断增长,确保应用系统的稳定性和可靠性变得至关重要。其中,限流是一种关键的技术手段,可以有效地防止系统因突发的高流量而崩溃。Nginx 作为一款强大的 Web 服务器和反向代理服务器,提供了多种方式来实现限流功能,下面我们将详细介绍。

一、为什么要进行限流

想象一下这样的场景:一个热门的电商平台在举办大型促销活动时,瞬间会有大量的用户同时访问网站,如果没有有效的限流措施,服务器可能会因为无法承受如此巨大的请求压力而崩溃,导致用户无法正常购物,甚至可能会造成数据丢失等严重后果。通过限流,我们可以将请求的数量控制在系统能够承受的范围内,保证系统的正常运行,提供良好的用户体验。

二、常见的限流算法及原理

  1. 固定窗口计数器算法原理:将时间划分为固定大小的窗口,比如每秒钟为一个窗口。在每个窗口内记录请求的次数。当请求到达时,如果当前窗口内的请求次数未超过设定的限制,则允许请求通过,否则拒绝请求。当窗口时间结束时,重置计数器,开始下一个窗口的计数。例如,我们设定每秒最多允许 10 个请求,在一个窗口时间内,每来一个请求就将计数器加一。如果计数器的值超过了 10,那么后续的请求就会被拒绝,直到下一个窗口开始重新计数。
  1. 漏桶算法原理:漏桶可以想象成一个底部有一个固定孔径的水桶。请求就像水一样以任意速率流入桶中,但是桶底部的水以一个固定的速率流出。无论流入的速率有多快,流出的速率是恒定的。当桶满时,新进入的请求会被拒绝。例如,我们设定漏桶每秒最多流出 5 个请求,不管有多少请求瞬间涌入,漏桶只会按照每秒 5 个的速度将请求传递给后端系统处理。如果请求的流入速度超过了流出速度,桶就会逐渐装满,当桶满时,后续的请求就无法进入桶中,也就是被拒绝了。
  1. 令牌桶算法原理:令牌桶以固定的速率生成令牌,并将令牌放入桶中。当请求到达时,需要从桶中获取一个令牌,如果桶中有令牌则请求被允许通过,否则拒绝请求。桶的容量是有限的,如果令牌生成的速度大于请求获取令牌的速度,桶中的令牌会逐渐积累,这就允许了一定程度的突发流量。例如,我们设定令牌桶每秒生成 8 个令牌,桶的最大容量为 20 个令牌。当请求到达时,如果桶中有令牌,就取出一个令牌让请求通过;如果桶中没有令牌,请求就被拒绝。如果在一段时间内请求的速率较低,令牌就会在桶中积累,当后续有突发的大量请求时,只要桶中还有令牌,这些请求就可以被快速处理。

三、使用 Nginx 实现限流的具体步骤

  1. 使用 Nginx 内置模块实现固定窗口计数器算法限流安装 Nginx(如果还未安装),并且确保安装的 Nginx 版本包含了ngx_http_limit_req_module模块(通常在编译 Nginx 时需要添加相应的配置参数来启用这个模块)。配置 Nginx:
http {
    # 设置共享内存区域,用于存储限流状态信息
    limit_req_zone $binary_remote_addr zone=my_limit_zone:10m rate=10r/s;

    server {
        listen 80;
        server_name example.com;

        location / {
            # 应用限流
            limit_req zone=my_limit_zone burst=5;
            proxy_pass http://backend;
        }
    }
}
  • 上述配置中:limit_req_zone指令定义了一个名为my_limit_zone的共享内存区域,用于存储每个客户端 IP($binary_remote_addr表示客户端的 IP 地址)的限流状态信息。这里设置的rate=10r/s表示每秒允许 10 个请求。limit_req指令应用限流规则,zone=my_limit_zone指定使用之前定义的区域,burst=5表示允许突发的额外请求数量为 5 个。这意味着在一个窗口时间内,如果请求瞬间超过了每秒 10 个的限制,但是只要总的请求数量在 10 + 5 = 15 个以内,这些额外的请求还是可以被接受的,但是后续的请求将会被拒绝,直到下一个窗口开始重新计数。
  • 重启 Nginx 使配置生效:根据不同的操作系统和安装方式,使用相应的命令重启 Nginx。例如,在 Linux 系统中,如果 Nginx 是通过系统服务管理的,可以使用sudo systemctl restart nginx命令。
  1. 使用 OpenResty 和 Lua 实现漏桶算法限流安装 OpenResty(它包含了 Nginx 和 LuaJIT 等)以及相关的 Lua 库。OpenResty 是一个基于 Nginx 和 LuaJIT 的高性能 Web 平台,它允许我们在 Nginx 中使用 Lua 脚本进行扩展和定制。编写 Lua 脚本实现漏桶算法逻辑:
-- 创建一个名为 leaky_bucket 的命名空间
local leaky_bucket = {}

-- 初始化漏桶状态
leaky_bucket.bucket_size = 10 -- 桶的容量
leaky_bucket.out_rate = 2 -- 流出速率(每秒处理的请求数)
leaky_bucket.current_size = 0 -- 当前桶中的请求数量
leaky_bucket.last_time = ngx.now() -- 记录上一次处理的时间

-- 漏桶限流函数
function leaky_bucket.limit()
    local now = ngx.now()
    -- 计算从上一次处理到现在经过的时间
    local elapsed_time = now - leaky_bucket.last_time
    -- 根据经过的时间和流出速率减少桶中的请求数量
    leaky_bucket.current_size = math.max(0, leaky_bucket.current_size - elapsed_time * leaky_bucket.out_rate)
    leaky_bucket.last_time = now

    -- 如果桶已满,拒绝请求
    if leaky_bucket.current_size >= leaky_bucket.bucket_size then
        return false
    else
        -- 增加当前桶中的请求数量
        leaky_bucket.current_size = leaky_bucket.current_size + 1
        return true
    end
end
  • 在 Nginx 配置文件中使用该 Lua 脚本进行限流:
http {
    lua_package_path "/path/to/your/lua/scripts/?.lua;;"; -- 设置 Lua 脚本路径,确保 Nginx 能够找到你的 Lua 脚本

    server {
        listen 80;
        server_name example.com;

        location / {
            content_by_lua_block {
                if not leaky_bucket.limit() then
                    ngx.exit(503) -- 返回服务不可用状态码,表示限流
                end
                -- 请求继续处理
                ngx.say("Hello, World!")
            }
        }
    }
}
  • 上述配置中:lua_package_path指令指定了 Lua 脚本所在的路径,这里需要根据实际情况将/path/to/your/lua/scripts/替换为你的 Lua 脚本实际存放的路径。在location块中,通过content_by_lua_block指令调用 Lua 脚本中的leaky_bucket.limit()函数进行限流判断。如果返回false,表示桶已满,此时通过ngx.exit(503)返回 503 服务不可用状态码给客户端,表示请求被限流;如果返回true,则表示请求可以通过,继续执行ngx.say("Hello, World!")向客户端返回响应内容。
  • 重启 OpenResty 使配置生效:同样根据不同的操作系统和安装方式,使用相应的命令重启 OpenResty。例如,在 Linux 系统中,如果 OpenResty 是通过系统服务管理的,可以使用sudo systemctl restart openresty命令。
  1. 使用 OpenResty 和 Lua 实现令牌桶算法限流同样需要先安装 OpenResty 和相关的 Lua 库。编写 Lua 脚本实现令牌桶算法逻辑:
local token_bucket = {}

token_bucket.bucket_capacity = 20 -- 令牌桶容量
token_bucket.token_rate = 10 -- 每秒生成的令牌数量
token_bucket.tokens = 0 -- 当前令牌数量
token_bucket.last_time = ngx.now()

function token_bucket.get_token()
    local now = ngx.now()
    -- 计算从上一次处理到现在新生成的令牌数量
    local tokens_to_add = math.floor((now - token_bucket.last_time) * token_bucket.token_rate)
    token_bucket.tokens = math.min(token_bucket.tokens + tokens_to_add, token_bucket.bucket_capacity)
    token_bucket.last_time = now

    -- 如果有令牌,则消耗一个令牌并返回 true,表示请求允许通过
    if token_bucket.tokens > 0 then
        token_bucket.tokens = token_bucket.tokens - 1
        return true
    else
        return false
    end
end
  • 在 Nginx 配置文件中应用令牌桶限流:
http {
    lua_package_path "/path/to/your/lua/scripts/?.lua;;";

    server {
        listen 80;
        server_name example.com;

        location / {
            content_by_lua_block {
                if not token_bucket.get_token() then
                    ngx.exit(503)
                end
                ngx.say("Hello from token bucket limited request!")
            }
        }
    }
}
  • 上述配置与漏桶算法的配置类似,主要区别在于调用的是token_bucket.get_token()函数进行令牌桶限流判断。如果返回true,表示桶中有令牌,允许请求通过;如果返回false,表示桶中没有令牌,请求被拒绝,通过ngx.exit(503)返回 503 状态码。
  • 重启 OpenResty 使配置生效。

四、总结

通过以上几种方式,我们可以在 Nginx 中实现有效的限流功能。在实际应用中,需要根据系统的特点和需求选择合适的限流算法和参数配置。同时,还需要不断地监控和调整限流策略,以适应不断变化的访问流量和系统负载情况,确保系统始终能够稳定、高效地运行。无论是固定窗口计数器算法的简单直接,还是漏桶算法和令牌桶算法的更灵活的流量控制,都为我们提供了强大的工具来保障系统的可靠性和性能。

上一篇:Nginx 面试通关秘籍-2.Nginx 是什么?它有哪些应用场景?

下一篇:Nginx 面试通关秘籍-4.Ribbon 和 Nginx 的区别是什么?

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言