slan's blog

有梦就去追,累了就休息

nginx request limit - Nginx 限流

限流是一个有效的保护服务的手段,nginx限流是一个便宜,可靠的限流方式,但是nginx的限流配置如果配置不好,可能给生产带来灾难。

官方默认示例配置:

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

    ...

    server {

        ...

        location /search/ {
            limit_req zone=one burst=5;
        }

建议详细参考这篇文档:https://www.nginx.com/blog/rate-limiting-nginx/ 然后详细测试再上生产。

一、限流不生效, return 的问题

如果你在location里直接返回了,然后左测右测限流都不生效,比如这样的配置:

server {
    location /search/ {
        limit_req zone=mylimit;
        
        return 200 'ok';
    }
}

那么小心了, return 是nginx直接返回,并不会进入到限流处理流程,所以限流不会生效。建议设置个root file来测试

二、速率控制

nginx限流速率的配置,无非是在rate burst delay 三个参数上配置,如何理解这三个参数,记住:

  • burst是缓冲区,请求进来先进入缓冲区,如果没设置,则可以当做缓冲区为0
  • rate是消费者,将请求以多少速度清出缓冲区,如果请求出缓冲区,则开始请求。
  • delay是控制缓冲区的请求是否其他发起。

举例1 rate=5 burst=0 delay不设置:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=0,所以没有缓冲,直接进rate消费
  3. rate=5/s=200ms/个,消费1个,剩下4个因为没到下个200ms,被丢弃,返回503

举例2 rate=5 burst=5 delay不设置:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit burst=5;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=5,有5缓冲,5个请求进入队列,5个503
  3. rate=5/s=200ms/个,消费1个,等待200ms
  4. 200-400ms,从burst清出1个,剩下3个
  5. 400-600ms,从burst清出1个,剩下2个
  6. 600-800ms,从burst清出1个,剩下1个
  7. 800-1000ms,从burst清出1个,剩下0个

这样虽然满足了解决了瞬间来的请求被丢弃的问题,但是带来了平均时延的上升,部分请求得等到1s才开始请求,平均延时增加了非常多。

举例3 rate=5 burst=5 nodelay:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit burst=5 nodelay;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=5,有5缓冲,5个请求进入队列,5个503
  3. delay开挂,在缓冲区直接开干了,5个队列中的直接请求
  4. 这个时候再来几个请求,依旧会503,因为队列没消下去。
  5. rate=5/s=200ms/个,消费1个,等待200ms
  6. 这个时候可以支持再来1个请求,被直接消费
  7. 200-400ms,从burst清出1个,剩下3个
  8. 400-600ms,从burst清出1个,剩下2个
  9. 600-800ms,从burst清出1个,剩下1个
  10. 800-1000ms,从burst清出1个,剩下0个
  11. 队列消完了,又可以支持瞬间来5个了。

这个配置可以支持:全局以x qps的速度,不影响时延的情况下进行限流,超过缓冲区大小的请求直接被503大部分的情况下建议用这个配置

举例4,限流时的并发保护,rate=5 burst=5 delay=2:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit burst=5 delay=2;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=5,有5缓冲,5个请求进入队列,5个503
  3. delay开挂,在缓冲区直接开干了,2个队列中的直接请求
  4. 这个时候再来几个请求,依旧会503,因为队列没消下去。
  5. rate=5/s=200ms/个,消费1个,等待200ms
  6. 这个时候可以支持再来2个请求,被直接消费
  7. 200-400ms,从burst清出1个,剩下3个
  8. 400-600ms,从burst清出1个,剩下2个
  9. 600-800ms,从burst清出1个,剩下1个
  10. 800-1000ms,从burst清出1个,剩下0个
  11. 队列消完了,又可以支持瞬间来5个了。

这种请求虽然缓冲区还是一样,但是缓冲区不是瞬间同时请求,可以起到保护后端的作用,但是设置不好还是有时延问题。一般情况下时延有要求的应用不推荐用。

限流测试

可以用jemeter等工具来测试,结合观察access.log,我比较喜欢用vegeta

echo "GET http://slan.localhost/"|vegeta attack -duration=5s -keepalive=true -rate=0 -max-workers=5 |tee results.bin|vegeta report

限流日志

$limit_req_status 是限流状态的变量,可以放到日志log_format中(1.17.6以上版本)

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$limit_req_status" "$request_time"';