---
title: "nginx 如何做针对 ip 的限流"
description: "闲白说到限流，大家一定能想到很多算法，比如 令牌桶 、漏桶 、计数器限流、 信号量 等等。"
date: 2024-07-23T14:10:00Z
canonical: https://xiaobox.github.io/p/2024-07-23-nginx-ru-he-zuo-zhen-dui-ip-de-xian-liu/
author: 小盒子
categories: ["系统底层"]
tags: ["Java", "Nginx", "Prompt", "算法", "微服务", "架构", "网络"]
source: https://mp.weixin.qq.com/s/v7Sb7uQqOpeF41262YbJSA
license: CC BY-NC-SA 4.0
license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/
---

# nginx 如何做针对 ip 的限流

## 闲白

![Image](https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-07-23-nginx-ru-he-zuo-zhen-dui-ip-de-xian-liu/001-c3921c64.jpg)

说到限流，大家一定能想到很多算法，比如  `令牌桶` 、`漏桶` 、`计数器限流`、 `信号量` 等等。解决方案也有很多，以 java 为例，`Guava` 库中的 RateLimiter 类 可以实现，`Semaphore` 类也可以实现。再复杂点儿，比如你是一个分布式微服务系统，可以上 `Hystrix`、`Resilience4j` 这种现成的方案。

从系统架构上来说，无非是在单体应用的当前进程中实现，还是分布式应用的非当前进程中之实现。当然还有另一种方案，就是不在业务应用中实现，而是把这种跟业务不那么紧耦合的功能抽象出去，**在网络层面对所有进入系统的请求进行统一的限流控制，这种方式的好处是可以避免每个微服务都实现自己的限流逻辑。**

现在很多 API 网关，尤其是新晋的 “云原生” 网关都具备这个功能（基本是标配），比如：Zuul、Kong、Ambassador、APISIX 等。

我们先不论系统是不是分布式微服务的，就单说限流这个事儿，其实也完全可以用 API 网关的思路来实现。就是我不用非要把代码写在应用中，如果我就是不想改代码呢？我想随时调整个限流策略还得重启应用？应用那么重，生效时间那么长，我可不想重启！

所以我们回头看看自己架构中的这些软件，一定能想到这位老朋友 **Nginx** 。当然无论是原味的 Nginx 还是跟它有血缘关系的 **openResty** 都一样。

想像一下，用 nginx 配置一下然后 `nginx -s reload` 就能搞定了，岂不痛快 ？!

## 正题

下文我们开始介绍在 nginx 怎么配置能实现针对某些（讨厌的）ip 进行限流，且不影响系统正常运行。（感叹：nginx 是个好东西！！！）

可能有些朋友看到标题就已经开始写 prompt 了，喝着 coffee 等着 **AI** 给你一行行输出答案，然后心里想：“什么年代了，大哥，还用写个文章专门说这事儿吗？你得学会用工具呀” 。

我想说的是，关于这个问题 AI 能给你回答对 90% 的内容，剩下的 10% 你得自己改。开发同学都知道 ，别说 10% 了，0.1% 不对，程序也不 work 呀。我是不会告诉你我花了一下午时间跟 AI 都聊了什么的。因为那显得我很弱智。

你也别抬杠说我用的工具不对，市面上但凡有的我都用了，真不行，所以我觉得还是值得写一下的。

### 配置详解

其实改的地方不多，首先我们要在 nginx 默认配置文件的 `http` 下面配置：

```
    geo $limit_ip {
        default 0;  # 默认为 0，表示不受限制
        1.2.3.4 1;  # 需要被限制的 IP
        # 添加更多需要限制的 IP 地址
    }

    map $limit_ip $limit_key {
      0 "";
      1 $binary_remote_addr;
    }

    # 定义限流区域
    limit_req_zone $limit_key zone=mylimit:10m rate=2r/s;

```

我们解释一下。

**geo 指令：**

geo 名字来源于“geographic”，意指地理位置。但是值得注意的是，geo 指令实际上只基于 IP 地址进行匹配，而 IP 地址与地理位置之间的映射需要额外的数据库或服务来提供。许多第三方服务和数据库（如 MaxMind GeoIP、GeoLite2 等）可以用来更精确地将 IP 地址转换为地理位置信息。

解释一下我们上文中中 geo 的配置：

-   `geo $limit_ip { ... }`：定义了一个名为 `$limit_ip`的变量，用于根据客户端 IP 地址设置不同的值。
-   `default 0;`：默认情况下，如果客户端 IP 地址不在列表中，`$limit_ip` 的值为 0。
-   `1.2.3.4 1;`：如果客户端 IP 地址是 1.2.3.4，则 `$limit_ip` 的值为 1。这里的 1 是一个标记，表示这个 IP 地址需要被限制。

总结来说就是用 geo 指令标记需要限制的 IP 地址

**map 指令：**

-   `map $limit_ip $limit_key { ... }`：根据`$limit_ip`的值来设置另一个变量`$limit_key`。
-   `0 "";`：如果`$limit_ip`的值为 0（即默认情况），则`$limit_key`的值为空字符串。
-   `1 $binary_remote_addr;`：如果`$limit_ip`的值为 1（即被标记的 IP 地址），则`$limit_key`的值为客户端 IP 地址的二进制形式（`$binary_remote_addr`）。

不知道聪明的你看出来没有，**我们这里其实设置的是 “黑名单” （即我想限制哪些 ip 我就配置哪些，剩下的不限制）**，在 geo 配置的 ip 到了 map 这里以后，将这些 IP 地址映射到了一个变量上，即 limit\_key 。**如果你想设置白名单（即我想让哪些 ip 不被限制我就配置哪些，剩下的都限制）不就是反过来操作嘛。**

举个白名单的例子：

```
geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/24 0;
    172.20.0.35 0;
}
map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}

```

### limit\_req\_zone

接着是整块配置的最后一行。

```
limit_req_zone $limit_key zone=mylimit:10m rate=2r/s;

```

使用 limit\_req\_zone 指令定义了一个限流区域，对标记的 IP 地址进行请求速率限制。如果一个 IP 地址不在 geo 指令中定义，则不受限制。如果一个 IP 地址被标记，则它的请求速率会被限制在每秒 2 个请求。

-   `$limit_key`：使用$limit\_key 变量作为限流的键。
-   `zone=mylimit:10m`：设置共享内存区域的大小为 10MB，用于存储限流信息。
-   `rate=2r/s`：设置每个键值（即每个 IP 地址）的请求速率限制为每秒 2 个请求。

其实这些指令都有一些详细参数，简单起见，我就不介绍了，都有 AI 了，需要的话自己查吧。我们说点儿重点。

我猜你可能关心  `zone=mylimit` 里面到底是什么样的，里面到底有啥 。是的，这很重要，了解清楚 zone 的结构很关键，关于 zone 的数据我没细看过，但结构大致类似这样：

```json
{
  "mylimit": {
    "123.124.210.242": {
      "current": 0,  // 当前请求计数
      "last": 1618305483,  // 上次请求的时间戳
      "tokens": 2,  // 当前令牌桶中的令牌数
      "delay": 0  // 由于限流导致的延迟（秒）
    },
    // ... 其他被限流的 IP 地址信息
    "192.168.1.100": {
      "current": 1,
      "last": 1618305495,
      "tokens": 1,
      "delay": 0
    }
  }
}

```

好了，到这里我们第一部分的配置就结束了，是不很简单？然后我们进行第二部分的配置，也很简单。

前文我们第一部分的配置只是定义了一个限流的策略，我们还没应用呢呀。所以我们要在需要的地方把它用起来。

很简单，在需要限流的 location 中这样写：

```
  location /abc/api {
       limit_req zone=mylimit;
  }

```

没了？就一句？

对，没了。是不很简单？简单到我都不想解释，如果你理解了前文你就懂了，我就不解释了。毕竟你会用 AI 不是。

然后你就可以重新加载配置，或重启 nginx 了。再然后你就要耐心等待和观察，等待之前那些讨厌的恶意 ip 再次造访，顺利地话你会在 nginx 的 error 日志中看到类似这样的信息 ：

```
... [error] ..limiting requests,excess:0.996 by zone "mylimit", client:1.2.3.4 ...

```

