nginx限流

Nginx 限流测试完全指南

一、快速开始

1. Nginx 配置(http 块)

http
    {
        
        set_real_ip_from 100.127.0.0/16;    # 腾讯云 CLB 实际使用的网段
        set_real_ip_from 10.206.0.0/24;     # 保留原有的(如果有用)
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
        # 全局限流:4000 req/s
        limit_req_zone $server_name 
                      zone=global:10m 
                      rate=80r/s;
                   
        # 限流区域定义
        limit_req_zone $binary_remote_addr zone=global_limit:10m rate=3r/s;# 添加全局限流
        limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

2. Nginx 配置(server 块)

server
{
    
    #PHP-INFO-START  PHP引用配置,可以注释或修改
    # include enable-php-81.conf;
    location ~ [^/]\.php(/|$){
        limit_req zone=global burst=30 nodelay;
        # limit_req zone=global_limit burst=2 nodelay;
        error_page 503 = @limit_json;
    
        try_files $uri =404;
        fastcgi_pass  unix:/tmp/php-cgi-81.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
        include pathinfo.conf;
    }

    
    location / {
      # 先过全局限流(总闸门)
      limit_req zone=global burst=30 nodelay;
      # 再过单IP限流(防单点)
      # limit_req zone=global_limit burst=2 nodelay;
      error_page 503 = @limit_json;
      
    	if (!-e $request_filename){
    		rewrite  ^(.*)$  /index.php?s=$1  last;   break;
    	}
    }
    location @limit_json {
        default_type application/json;
        return 200 '{"code":429,"msg":"前方拥挤,请稍后重试"}';
    }


3. k6 测试脚本

import http from 'k6/http';
import { Counter } from 'k6/metrics';

const limitedRequests = new Counter('limited_requests');
const normalRequests = new Counter('normal_requests');

export let options = {
    // 方案 1:固定高并发(同步,会被响应时间阻塞)
    // vus: 50,
    // duration: '10s',
    
    // 方案 2:固定请求速率(不受响应时间影响)
    scenarios: {
        constant_request_rate: {
            executor: 'constant-arrival-rate',
            rate: 3000,           // 每秒发送 1500 个请求 ⚠️如果这里的vu数不够 很难测试准确限流数
            timeUnit: '1s',       // 时间单位
            duration: '10s',      // 持续 10 秒
            preAllocatedVUs: 100, // 预分配 100 个 VU
            maxVUs: 500,          // 最多 500 个 VU(应对慢响应) ⚠️如果这里的vu数不够 很难测试准确限流数
        },
    },
    
    // 方案 3:阶梯式压测(注释掉上面的,启用下面的)
    // stages: [
    //     { duration: '5s', target: 100 },
    //     { duration: '10s', target: 100 },
    //     { duration: '5s', target: 0 },
    // ],
};

export default function () {
    // 单台服务器测试
    let res = http.get('http://domain:8001/index/index/index');

    
    // CLB 负载均衡测试
    // let res = http.get('http://domain');
    
    // 判断是否被限流
    let isLimited = false;
    try {
        const body = JSON.parse(res.body);
        if (body.code === 429 || body.code === '429') {
            isLimited = true;
            limitedRequests.add(1);
            // 打印限流响应
            console.log(`🚫 [限流] 状态码: ${res.status} | 响应: ${res.body}`);
        } else {
            normalRequests.add(1);
            // 打印正常响应(只显示前100字符)
            console.log(`✅ [通过] 状态码: ${res.status} | 响应: ${res.body.substring(0, 100)}...`);
        }
    } catch (e) {
        normalRequests.add(1);
        // 打印HTML响应(只显示前100字符)
        console.log(`✅ [通过] 状态码: ${res.status} | HTML响应: ${res.body.substring(0, 100)}...`);
    }
    
    // 不要 sleep,让请求尽可能快
}

export function handleSummary(data) {
    const totalReqs = data.metrics.http_reqs.values.count;
    const rate = data.metrics.http_reqs.values.rate;
    const limited = data.metrics.limited_requests ? data.metrics.limited_requests.values.count : 0;
    const normal = data.metrics.normal_requests ? data.metrics.normal_requests.values.count : 0;
    
    console.log('\n========== 限流测试结果 ==========');
    console.log(`总请求数: ${totalReqs}`);
    console.log(`实际速率: ${rate.toFixed(2)} req/s`);
    console.log(`正常通过: ${normal} 次 (${(normal/totalReqs*100).toFixed(1)}%)`);
    console.log(`被限流:   ${limited} 次 (${(limited/totalReqs*100).toFixed(1)}%)`);
    console.log('');
    
    if (rate < 40) {
        console.log('⚠️ 实际速率低于预期,可能是:');
        console.log('   1. 网络延迟较高');
        console.log('   2. 服务器响应慢');
        console.log('   3. 并发数不够');
    } else if (limited === 0) {
        console.log('❌ 限流未生效!');
    } else if (limited / totalReqs > 0.5) {
        console.log('✅ 限流正常工作!');
    } else {
        console.log('⚠️ 限流部分生效');
    }
    
    // return {
    //     'stdout': JSON.stringify(data, null, 2),
    // };
}

4. 运行测试

k6 run high_concurrency_test.js

二、核心概念

关键参数

参数 说明 示例
rate 每秒允许的请求数 80r/s = 每秒 80 个请求
burst 突发流量缓冲区 30 = 可额外处理 30 个请求
nodelay 立即处理 不排队,立即返回

Token Bucket 算法

核心公式

总通过数 = rate × 时间 + burst

示例

rate=80r/s, burst=30, 测试 10 秒
理论通过 = 80 × 10 + 30 = 830 个

三、参数配置

k6 配置建议

k6 rate = Nginx 限流 × 25-50 倍

示例:
Nginx 限流 = 80 req/s
k6 rate = 2000-4000 req/s

maxVUs = rate × 响应时间
示例:
rate = 3000, 响应时间 = 0.2s
maxVUs = 3000 × 0.2 = 600

四、常见问题(大白话)

Q1: 为什么 VU 要等待?

银行例子

员工去银行办事:
1. 去银行(发送请求)
2. 等待办理(等待响应)⏳ 必须等!
3. 办完了(收到响应)
4. 才能再去下一次

办事快(10ms):1 个员工 10 秒能去 1000 次 ✅
办事慢(747ms):1 个员工 10 秒只能去 13 次 ❌

关键

  • Nginx 很快(< 1ms)
  • 但要等整个链路(包括 PHP)
  • 慢的是 PHP,不是 Nginx

实际数据

你的测试(固定 50 VU):
响应时间:747ms
每个 VU:10,000ms ÷ 747ms = 13.4 次
总请求:13.4 × 50 = 670 次

问题:VU 被阻塞,发送请求太少

Q2: 提高 rate 和 maxVUs,通过数会增加吗?

答案:不会!

银行规定:每分钟办理 80 人(Nginx 限流)

你派 200 个员工:银行接待 830 人
你派 500 个员工:银行接待 830 人
你派 1000 个员工:银行接待 830 人

无论派多少员工,银行只接待 830 人!

数据验证

rate maxVUs 总请求 通过 通过率
1500 200 3,163 750 23.7%
1500 500 14,674 837 5.7%
3000 500 29,515 829 2.8%

结论

  • 总请求增加 ✅
  • 通过数不变(~830)✅
  • 通过率下降 ✅

Q3: 为什么第一次只通过 750 个?

问题

配置:rate=80r/s, burst=30
理论通过:830 个
实际通过:750 个
差距:80 个(9.6%)

原因(银行例子)

你派了 200 个员工,办事慢(588ms)

第 1 分钟:
- 110 人进去了(80+30)
- 这 110 人在办事(需要 35 秒)
- 剩余员工:200 - 110 = 90 人

第 2 分钟:
- 80 人进去了
- 剩余员工:90 - 80 = 10 人
- ⚠️ 前面 110 人还在办事中

第 3 分钟:
- 10 人进去了
- 剩余员工:10 - 10 = 0 人
- ❌ 所有 200 个员工都在银行里等着
- ❌ 没有空闲员工继续排队了!

第 4-10 分钟:
- 没有员工可以去排队
- 虽然银行还能接待人
- 但你没人去了!

结果:
- 只办理了 750 人
- 不是银行的问题
- 是你的员工不够用!

实际数据

测试 1(maxVUs=200):
总请求:3,163 个
正常通过:750 个
实际速率:157 req/s
VUs 使用:max=200(用满了)❌
dropped_iterations: 1838(丢弃了很多)

测试 2(maxVUs=500):
总请求:14,674 个
正常通过:837 个
实际速率:983 req/s
VUs 使用:max=426(没用满)✅
dropped_iterations: 327(少很多)

结论:
- maxVUs 不够 → VU 用满 → 测试不完整 ❌
- maxVUs 够用 → 测试正常 → 结果准确 ✅

五、测试结果

完美的测试

配置:rate=80r/s, burst=30
k6:rate=3000, maxVUs=500

结果:
总请求:29,515 个
通过:829 个
被限流:28,686 个

验证:
理论:830 个
实际:829 个
误差:0.12% 🎯

六、银行例子(大白话)

银行规定

银行:每分钟办理 80 人,排队区 30 人
10 分钟总共接待:830 人

这是银行规定,不会变!

你的测试演进

测试 1:派 50 个员工(固定 VU)

办事慢(747ms)
每个员工 10 秒去 13 次
50 个员工去了 650 次
银行接待 627 人(91.8%)

问题:
- 员工太少
- 被响应时间阻塞
- 银行没忙起来 ❌

测试 2:派 200 个员工(maxVUs 不够)

办事慢(588ms)
前 3 分钟:200 个员工用完了
后 7 分钟:没有空闲员工了
员工去了 3,163 次
银行接待 750 人(23.7%)

问题:
- 正在办理的占用了员工
- 没有空闲员工继续排队
- 测试不完整 ❌

测试 3:派 500 个员工(maxVUs 够用)

办事慢(224ms)
有些员工在办事,有些继续排队
员工去了 14,674 次
银行接待 837 人(5.7%)

效果:
- 员工够多
- 能持续排队
- 限流充分触发 ✅

测试 4:派 500 个员工,跑快点(完美)

rate=3000(更快)
员工去了 29,515 次
银行接待 829 人(2.8%)

效果:
- 压力更大
- 测试更准确
- 误差 0.12% 🎯

关键理解

1. 办事快 vs 慢
   - 快(10ms):1 个员工 10 秒去 1000 次 ✅
   - 慢(747ms):1 个员工 10 秒去 13 次 ❌

2. 员工够不够(重点!)
   - 50 个员工:被阻塞,去 650 次 ❌
   - 200 个员工:用完了,去 3,163 次 ❌
   - 500 个员工:够用,去 14,674 次 ✅

3. 正在办理的也算
   - 200 个员工,前 3 分钟用完
   - 因为正在办理的占用了员工
   - 没有空闲员工继续排队了

4. 银行规定不变
   - 无论派多少员工
   - 银行只接待 830 人!

七、总结

核心公式

总通过数 = rate × 时间 + burst

示例:rate=80r/s, burst=30, 10秒
理论:80 × 10 + 30 = 830 个

关键点

1. Nginx 很快(< 1ms)
2. VU 要等整个链路(包括 PHP)
3. 固定 VU 会被阻塞
4. 固定速率不受影响
5. 通过数由 Nginx 决定

测试目标

验证限流配置正确 ✅
测试限流效果 ✅
确保系统稳定 ✅

完成! 🎉

posted @ 2025-11-29 14:54  窦戈  阅读(0)  评论(0)    收藏  举报