0%

限制某个接口服务的 QPS,本质上就是做限流。

但是实际项目里说“限制 QPS”,往往还不够准确。因为在真正落地时,通常还要先回答几个问题:

  • 按什么维度限流
  • 是单机服务还是多实例服务
  • 是否允许突发流量
  • 超限后是直接拒绝,还是排队等待

如果这些问题没有先想清楚,后面的实现方式也很容易选错。

这篇文章整理一下常见的限流方案,以及在实际项目中如何选择。

一、常见限流算法

几种常见限流算法示意:

1. 固定窗口限流

固定窗口的思路很简单,就是按时间窗口统计请求数。

例如:

  • 1 秒内最多允许 100 次请求

实现方式通常是:

  • 记录当前秒的请求次数
  • 超过阈值就拒绝

优点:

  • 实现简单
  • 适合快速上线

缺点:

  • 容易出现临界点突刺

比如上一秒末尾来了 100 次请求,下一秒开头又来了 100 次请求,那么虽然每个窗口都没超限,但实际瞬时流量可能接近 200。

适合场景:

  • 简单后台接口
  • 对限流精度要求不高的场景

2. 滑动窗口限流

滑动窗口可以理解为对固定窗口的优化。

它不会只看一个大窗口,而是把大窗口拆成多个小格子,然后统计最近一段时间内的总请求数。

例如:

  • 1 秒限 100 次
  • 拆成 10 个 100ms 小窗口
  • 每次请求时统计最近 10 个格子的总和

优点:

  • 比固定窗口更平滑
  • 能有效减少临界点突刺

缺点:

  • 实现复杂度比固定窗口高一些

适合场景:

  • 对流量波动比较敏感的接口
  • 希望限流更平滑的服务

3. 令牌桶

令牌桶是实际项目里非常常见的一种方案。

它的核心思路是:

  • 系统按固定速率往桶里放令牌
  • 请求来了先取令牌
  • 取到令牌才允许通过

例如:

  • 每秒产生 100 个令牌
  • 桶最大容量 200
  • 每个请求消耗 1 个令牌

优点:

  • 能限制平均 QPS
  • 同时允许一定程度的突发流量

缺点:

  • 需要维护桶状态
  • 实现复杂度比纯计数器略高

适合场景:

  • 大多数常规接口限流
  • 既要控平均流量,又不想完全抹平突发流量的场景

4. 漏桶

漏桶的思路是把请求先放进桶里,再按固定速率流出处理。

优点:

  • 输出速度非常平稳
  • 对后端服务的保护效果很直接

缺点:

  • 突发请求容易排队
  • 队列满了以后就会丢弃
  • 对实时 HTTP 接口不一定友好

适合场景:

  • 需要平滑消费的系统
  • 更偏内部异步处理的链路

二、实际项目中,先明确“按什么维度限流”

做限流时,真正关键的往往不是算法本身,而是限流维度。

1. 按接口整体限流

例如:

  • /api/order/create 最大 100 QPS

这种方式主要是保护服务总容量。

2. 按用户限流

例如:

  • 每个用户每秒最多 5 次

这种方式适合防止单个用户刷接口。

3. 按 IP 限流

例如:

  • 每个 IP 每秒最多 20 次

更适合未登录接口,比如公开查询接口、验证码接口。

4. 按客户端 ID / 设备 ID 限流

例如:

  • 每台客户端每秒最多 3 次

适合桌面客户端、设备端或者 IoT 场景。

很多时候,一个系统不会只做一种维度,而是组合使用:

  • 网关层按 IP 或接口总量限流
  • 业务层按用户、设备、租户再做补充限流

三、超限后怎么处理

常见策略一般有三种。

1. 直接拒绝

这是最常见的做法,通常返回:

  • HTTP 429 Too Many Requests

优点是实现简单,也符合大多数 HTTP 接口的预期。

2. 排队等待

这种方式更适合内部异步任务,不太适合普通实时接口。

因为对大多数 HTTP 请求来说,用户更关心的是快速失败还是快速成功,而不是无限等待。

3. 降级

例如:

  • 返回缓存数据
  • 返回默认值
  • 返回简化结果

这种方式适合一些“可以退而求其次”的接口。

四、单机服务怎么做

如果当前只有一个服务实例,限流可以直接在应用内存中做。

例如在 Node.js 服务里,最简单的方式就是用计数器做一个固定窗口限流。

下面是一个“1 秒最多 10 次”的基础示例:

const express = require('express')
const app = express()

let count = 0
let currentSecond = Math.floor(Date.now() / 1000)

app.use('/api/test', (req, res, next) => {
const nowSecond = Math.floor(Date.now() / 1000)

if (nowSecond !== currentSecond) {
currentSecond = nowSecond
count = 0
}

count++

if (count > 10) {
return res.status(429).json({
code: 429,
message: '请求过多,请稍后再试'
})
}

next()
})

app.get('/api/test', (req, res) => {
res.json({ ok: true })
})

app.listen(3000)

这个实现很基础,但对于单实例的小项目来说已经够用了。

它本质上就是固定窗口限流。

单机固定窗口限流示意:

五、多实例服务怎么做

如果接口服务已经部署成多个实例,就不能只靠本地内存了。

原因很简单:

  • 每个实例都会各自统计请求数
  • 每台机器都可能各自放行一部分请求
  • 最终整体 QPS 仍然可能超限

这时候常见的做法有三类:

  • Redis 限流
  • 网关层限流
  • Nginx / Kong / APISIX / Envoy / Spring Cloud Gateway 等统一入口限流

1. Redis 限流

Redis 方案的核心是:

  • 所有实例共用一份计数或桶状态

常见实现方式有:

  • INCR + EXPIRE
  • Lua 脚本保证原子性
  • 用 Redis 实现固定窗口、滑动窗口或令牌桶

Redis 更适合:

  • 多实例部署
  • 需要统一限流状态
  • 业务层需要按用户、设备、租户细分规则

2. 网关层限流

这是我更推荐的方式之一。

优点很明显:

  • 不侵入业务代码
  • 所有请求都会先经过网关
  • 规则配置和监控更统一

常见限流维度包括:

  • 整个接口
  • IP
  • 用户 ID
  • 客户端 ID
  • 租户
  • token

网关层适合控整体流量和统一入口规则,业务层则适合处理更细的业务逻辑。

多实例统一限流示意:

六、Nginx 也可以做简单 QPS 限制

如果你的服务前面本身就有 Nginx,那么可以直接做一层基础限流。

例如按 IP 限流:

http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
location /api/test {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
}

这段配置的含义是:

  • 平均每秒 10 个请求
  • 允许一定突发流量,突发上限 20
  • 超过后开始限流

这种方式适合:

  • 简单网关层防刷
  • 未登录接口保护
  • 低成本增加第一道流量防线

七、实际项目里应该怎么选

如果只看“能不能实现”,其实很多方式都能做。

真正要考虑的是:

  • 部署架构
  • 业务复杂度
  • 是否有统一网关
  • 是否需要动态调整规则
  • 是否需要白名单
  • 是否需要区分普通用户和 VIP 用户

1. 简单项目

建议:

  • 单机服务:应用内存限流
  • 一两台机器:Redis 限流

2. 正式生产环境

建议:

  • 网关层限流为主
  • 业务层做补充
  • 核心接口再配合 Redis 或网关统一控制

3. 最常见组合

我自己更倾向这种组合:

  • 网关层:控总量、控 IP
  • 业务层:控用户、控设备、控特殊规则

这种分层方式比较清晰,也更容易维护。

八、如果是 Node.js 服务,我会怎么建议

如果你的服务本身是 Node.js,通常可以先按下面这个顺序考虑。

最小实现

  • 单实例:直接在代码里做固定窗口或令牌桶
  • 多实例:Redis + Lua 脚本
  • 如果前面有 Nginx:先让 Nginx 做第一层基础限流

这样做的好处是:

  • 先用最小成本解决问题
  • 后面随着服务规模增长再逐步升级

九、最后几个关键点

做限流时,不要只盯着“QPS 限多少”这个数字。

还有这些问题也很重要:

  • 限流维度是什么
  • 是单机还是多实例
  • 是否允许突发流量
  • 超限后是拒绝还是排队
  • 是否需要白名单
  • 是否要区分普通用户和 VIP 用户
  • 是否要支持动态调整阈值

很多线上问题并不是“没有限流”,而是“限流规则设计得不对”。

十、小结

如果只是想快速理解怎么限制一个接口服务的 QPS,可以先记住这几点:

  • 固定窗口简单,但有临界突刺问题
  • 滑动窗口更平滑
  • 令牌桶最常用,兼顾平均速率和突发流量
  • 漏桶更适合平滑消费
  • 单机服务可以先在内存里做
  • 多实例服务更适合 Redis 或网关统一限流

如果是实际生产环境,我通常更建议:

  • 网关层控制总量和 IP
  • 业务层补充用户、设备、租户等细粒度规则

这样整体会更稳,也更容易扩展。

参考文章

在 mac 上安装 Docker 时,很多人第一反应是直接安装 Docker Desktop。

但是有些场景下,我们其实并不需要完整的桌面客户端,只想要命令行,也就是只使用 docker CLI。

比如:

  • 只想连接远程 Linux 服务器上的 Docker 服务
  • 本地不想安装 Docker Desktop
  • 希望用更轻量的方式在命令行里操作容器

这里介绍两种常见方式:

  • 只安装 docker 命令行工具
  • 安装 docker CLI,并配合 colima 在本机运行容器

一、先说明一下区别

docker 这个命令本身只是客户端,真正负责运行容器的是 Docker daemon。

在 Linux 上,通常是 Docker Engine 提供 daemon;
而在 mac 上,因为没有原生 Linux 容器运行环境,所以如果想在本机运行容器,一般还需要一个虚拟化运行环境。

所以:

  • 如果你只是连接远程 Docker 主机,那么安装 CLI 就够了
  • 如果你想在当前这台 Mac 上直接执行 docker run 跑容器,那么除了 CLI,通常还要再配一个本地 runtime,比如 colima

二、只安装 Docker CLI

如果你只是想保留命令行工具,不安装 Docker Desktop,可以直接通过 Homebrew 安装:

brew install docker

安装完成后查看版本:

docker --version

或者:

docker version

如果当前还没有可连接的 Docker daemon,那么执行 docker version 时,通常会看到客户端信息,但服务端可能连接失败,这属于正常现象。

安装效果示意:

三、连接远程 Docker 主机

如果你的 Docker daemon 在远程 Linux 服务器上,那么本机只装 CLI 就可以了。

1. 直接通过环境变量连接

比如远程主机监听在:

tcp://192.168.1.10:2375

可以先设置:

export DOCKER_HOST=tcp://192.168.1.10:2375

然后执行:

docker ps

2. 使用 docker context

相比直接写 DOCKER_HOSTdocker context 更适合长期使用。

创建一个远程 context:

docker context create remote-server --docker "host=tcp://192.168.1.10:2375"

切换到这个 context:

docker context use remote-server

查看当前 context:

docker context ls

这样后续直接执行 docker psdocker images 就会作用在对应的远程主机上。

远程 context 使用示意:

四、如果想在本机运行容器,可以配合 colima

如果你不想安装 Docker Desktop,但又想在本机直接运行容器,那么一个常见做法是:

  • 安装 docker
  • 安装 colima
  • colima 提供本地运行环境

1. 安装 colima

brew install colima

2. 启动 colima

colima start

colima 默认会使用 Docker runtime,启动成功后,docker CLI 就能直接连接到它。

3. 验证是否正常

docker version

如果服务端信息也能正常显示,说明当前本机已经可以直接运行容器了。

再试一个简单例子:

docker run --rm hello-world

如果输出欢迎信息,说明整套 CLI + 本地 runtime 已经可以正常工作。

本地通过 colima 运行容器示意:

五、常用命令

1. 查看当前 Docker context

docker context ls

2. 切换 context

docker context use default

或者:

docker context use remote-server

3. 停止 colima

colima stop

4. 重新启动 colima

colima start

六、常见问题

1. 安装了 docker 但执行命令提示无法连接 daemon

这通常不是 CLI 没装好,而是当前没有可用的 Docker daemon。

可以按自己的场景检查:

  • 是否打算连接远程 Docker 主机
  • 是否已经设置 DOCKER_HOST
  • 是否已经切换到正确的 docker context
  • 如果是本机运行,是否已经执行 colima start

2. 我只安装 CLI,能不能直接在本机跑容器?

一般不行。

因为 mac 上只有 CLI 还不够,本地还需要一个实际运行容器的环境。colima、Docker Desktop 这类工具,本质上都是在帮你解决这个问题。

3. 只安装 CLI 有什么好处?

比较适合下面几类场景:

  • 只连接远程服务器
  • 本机不希望常驻桌面程序
  • 希望安装更轻量

七、小结

如果你只是想在 mac 上使用 Docker 命令行,而不安装完整客户端,那么最简单的方式就是:

brew install docker

如果只是连接远程 Docker 主机,到这里就够了。

如果还想在本机运行容器,又不想装 Docker Desktop,那么可以继续安装:

brew install colima
colima start

这样就可以保留纯命令行的使用方式,同时又能在本机正常执行 docker run

参考文章

catimg 是一个可以直接在终端里显示图片的小工具,适合在纯命令行环境里快速预览图片,也可以拿来做一些有意思的终端展示。

它支持常见图片格式,常用场景有:

  • 在服务器或终端环境快速预览图片
  • 在 SSH 环境下简单查看图片内容
  • 在终端里展示 PNG、JPG、GIF 等图片

一、安装 catimg

1. 优先使用 apt 安装

先更新软件包索引:

sudo apt update

安装 catimg

sudo apt install catimg

安装过程示意:

安装完成后查看是否成功:

catimg -h

如果终端输出帮助信息,说明安装成功。

2. 如果 apt 仓库里没有,再使用源码安装

有些 Ubuntu 环境默认仓库里可能没有 catimg,这时可以从源码编译安装。

先安装编译工具:

sudo apt update
sudo apt install git cmake build-essential

拉取源码:

git clone https://github.com/posva/catimg.git
cd catimg

编译并安装:

cmake .
make
sudo make install

安装完成后检查版本或帮助信息:

catimg -h

二、基本使用

最简单的用法就是直接指定一张图片:

catimg demo.png

如果终端支持 256 色,图片就会直接输出到当前终端窗口中。

预览效果示意:

三、常用参数

1. 指定显示宽度

使用 -w 指定图片显示宽度:

catimg -w 80 demo.png

这里的 80 表示图片输出宽度。终端较窄时,手动指定宽度会更容易控制显示效果。

2. 显示 GIF 并控制循环次数

catimg 支持 GIF,使用 -l 指定循环次数:

catimg -l 3 demo.gif

如果设置为负数,则表示一直循环:

catimg -l -1 demo.gif

3. 指定分辨率模式

可以通过 -r 指定分辨率模式:

catimg -r 1 demo.png

或者:

catimg -r 2 demo.png

一般来说:

  • -r 1 显示更保守
  • -r 2 显示更细一些

可以根据自己的终端效果选择。

4. 限制颜色转换

有些终端颜色支持较差,可以加上 -c

catimg -c demo.png

这样可以让颜色映射更稳定一些。

常用参数组合示意:

四、使用示例

1. 预览当前目录中的图片

catimg ./test.png

2. 按指定宽度显示

catimg -w 120 ./test.jpg

3. 在 SSH 终端里查看图片

catimg -w 60 ./qrcode.png

如果只是想快速确认图片内容,比如二维码、截图、流程图缩略图,这种方式还是很方便的。

五、常见问题

1. 终端显示效果很差

catimg 依赖终端的颜色能力,如果当前终端不支持 256 色,显示效果会比较一般。

可以先检查终端类型:

echo $TERM

常见比较合适的终端有:

  • xterm-256color
  • screen-256color
  • tmux-256color

2. 图片太大,看不全

可以直接用 -w 限制宽度:

catimg -w 50 big.png

3. 命令不存在

如果提示:

catimg: command not found

可以按下面几个方向检查:

  • 确认是否已经安装成功
  • 如果是源码安装,确认 /usr/local/bin 是否在 PATH
  • 重新打开一个终端再试

查看命令路径:

which catimg

六、小结

catimg 很适合在 Ubuntu 终端中快速查看图片,安装也比较简单。

如果仓库中能直接安装,优先使用:

sudo apt install catimg

如果仓库里没有,再使用源码编译安装即可。

参考文章