一次后端接口限流实践

560 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

问题背景

前段时间,安全同事提出我们的系统存在无限量发送验证码的问题。系统的整体业务流如下图。

未命名文件 (4).jpg

用户请求经过 nginx 到网关,再到业务层,业务层调用 push 模块发送信息。 push 模块封装了对公司的统一通知平台的调用。公司的统一通知平台提供了发送短信、邮件等服务,对单个用户的发送量没有做限制。如果用户对发送验证码的接口发送大量恶意请求,由于没有做合理限制,将给服务器带来压力。

解决思路

一种解决思路是在发送短信或邮件之前让用户先进行验证,比如增加极验验证码。但接入极验验证码是需要 monny 的,解决这个问题也可以通过限流,控制一定时间内发送的请求量。

nginx 限流

提到限流,我们可能会想到 nginx 可以限流。nginx 的 limit_req 模块能够限制每个客户端的每秒请求数,使用的是漏桶算法,把突发的流量限定为每秒处理多少个请求。在 nginx.conf 中有如下配置:

http
{
     limit_req_zone $binary_remote_addr zone=reqlimit:10m rate=60r/m;
     server
     {
          listen  80;
          location /api/ {
              limit_req zone=reqlimit burst=2 nodelay;
          }
     }
}

nginx 的限流配置先在http模块声明 limit_req_zone。参数的含义如下:

  • $binary_remote_addr: 是限流的 key ,表示 ip 地址,将根据 ip 地址限流。
  • zone :是声明需要一块内存空间。
  • reqlimit :内存的 name 。
  • 10m:表示这块内存的大小为10M。
  • rate = 60r/m:表示限流速率,单位是 r/m 或者 r/s。

在 location 模块通过指令 limit_req 进行限流。参数 zone=reqlimit 表示使用我们刚才声明的内存空间。burst 是表示漏桶算法里的桶有多大,如果桶满了,返回错误,桶没满,但达到了限制速率,不会返回错误,请求会放到桶里,响应变慢。nodelay 配置会让桶里的请求不采用延时处理,而是马上处理。

nginx 针对 location 块在网关层面进行限流。

redis 限流

有时候我们需要接口层面的限流。

通过计数算法来限流,比如一分钟只能访问10次,如果没有到达10次,将访问次数累加,到达访问次数,就返回错误。一分钟后清零重新开始计算。redis 中间件由于它的基于内存访问速度块和原子性操作可以被我们用来实现限流。

限流的 key 可以选择是客户端 ip ,但要注意这个 ip 需要是客户端的真实ip,这里我是将收件人作为限流的 key。

//配置文件里配置了 seconds 时间内最多访问 limitCount 次
int limitCount = systemConfig.getLimitCount();
int seconds = systemConfig.getSeconds();

//根据 key 获取 count ,并加1,如果 key 为 null 会返回1
long count = redisTemplate.opsForValue().increment(key);

if (count <= limitCount) {
     //第一次访问时,设置redis中key的过期时间
     if (count == 1) {
        redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
     }
} else {
     //超过最大限制访问次数
}

如果是在高并发的场景下使用,可以写 Redis 的 lua 脚本文件来实现限流。

题外话

上周收到了二月更文挑战的奖品,这是第一次因为写文章收到的礼品,也是一种激励,开森,纪念一下,再接再励。

微信图片_20220403103824.jpg

如果有错误的地方欢迎大家指出!