简介:本资源是一套面向网络安全研究人员与防御工程师的DDoS攻击原理与工具实践教学包,聚焦于发包机系统搭建、流量生成机制及常见反射放大攻击(如DNS/SSDP/NTP/UDP Flood等)的技术实现。资源共152个文件,涵盖38个C语言攻击模块(含SYN、ACK、RST、FIN、Slowloris等变种)、31个网络数据包样本(.pkt)、24个PHP服务端利用脚本、10个说明文档(.txt/.md),以及关键视频教程(.wmv)和源码级攻击脚本(.py/.sh/.c)。压缩包大小为50.83MB,结构清晰,按扫描探测、目标过滤、协议构造、流量放大、日志分析等逻辑分层组织。已有3663人学习下载,可帮助读者深入理解DDoS攻击链各环节,掌握真实环境下的工具编译、参数调优与流量特征模拟方法,为渗透测试复现、WAF规则验证及防御策略制定提供可运行的技术支撑。
1. 项目概述:从零构建你的网络压力测试环境
最近在折腾一些网络应用,无论是自己写的Web服务,还是给公司内部系统做性能摸底,总绕不开一个问题:这东西到底能扛住多少并发?模拟真实用户请求,看看服务在高负载下的表现,这就是网络压力测试的核心。而“发包机”,就是执行这项任务的专用工具或环境。它本质上是一台或多台配置好的机器,运行着特定的脚本或程序,能够按照预设的规则,向目标服务器发起海量的网络请求(数据包),从而模拟出真实的访问压力。
你可能听过JMeter、Locust这些大名鼎鼎的压力测试工具,它们功能强大但配置繁琐,环境依赖多。对于需要快速验证、定制化流量模型,或者想在纯净的Linux环境下进行底层网络测试的场景,一套轻量、可控、可脚本化定制的发包方案就显得尤为实用。这就是我们今天要搭建的“全套发包机”的价值所在——它不是一个单一的软件,而是一个从系统准备、工具安装、脚本编写到执行监控的完整解决方案。尤其对于运维、开发和安全测试人员来说,掌握这套方法,意味着你拥有了随时对任何网络服务进行“压力体检”的能力。
本教程将带你从一台干净的Linux服务器(以Ubuntu 22.04为例)开始,逐步搭建起一个功能完整的发包环境。核心会围绕Shell脚本的编写展开,因为Shell脚本是在Linux环境下实现自动化、定制化发包逻辑最直接、最高效的方式。我们会涉及基础命令工具的使用、循环与控制结构的编写、结果记录与分析,最终形成一个可以一键执行、参数可调、结果可视化的自动化测试脚本集。无论你是想测试API接口的QPS(每秒查询率),还是验证Web服务器的并发连接处理能力,这套方法都能为你提供一个坚实的起点。
2. 核心工具链选型与底层原理
搭建发包机,工具选型是第一步。我们的目标是轻量、高效、系统原生支持或易于安装,并且足够灵活以满足不同的测试场景。下面拆解几个核心组件及其背后的考量。
2.1 网络请求生成器:curl、ab、wrk与hping3
发包的核心是生成HTTP或TCP/UDP请求。我们根据协议和复杂度分层选择:
curl(Client URL):这是我们的“瑞士军刀”。它支持数十种协议(HTTP/HTTPS/FTP等),功能极其丰富,可以自定义请求头、方法、体、超时、重试等几乎所有HTTP参数。对于需要复杂交互、携带Cookie、处理重定向的API测试,curl是不二之选。它的单次请求功能强大,但原生不适合高并发,需要结合Shell脚本的并发控制(如&后台执行或xargs -P)来施展威力。ab(Apache Benchmark):Apache服务器自带的一个简单易用的HTTP基准测试工具。它的优势是开箱即用,一条命令就能发起固定并发数的总请求量测试,并直接输出汇总报告,包括每秒请求数、请求时间分布等。缺点是功能相对单一,无法灵活定制请求序列(比如不同接口按比例混合),且在高并发下其自身性能可能成为瓶颈。它适合做快速的、标准的HTTP性能摸底。wrk:一个现代的、高性能的HTTP基准测试工具,采用多线程+事件驱动模型,能用很少的系统资源产生巨大的压力。它支持通过Lua脚本来自定义请求、生成动态参数、处理响应,灵活性远超ab。对于追求极致HTTP压测性能和灵活测试场景的开发者,wrk是首选。本教程后续的高级脚本示例会引入wrk。hping3:这是一个面向TCP/IP层的工具,可以组装和发送任意自定义的原始TCP、UDP、ICMP数据包。它常用于网络安全领域的防火墙规则测试、端口扫描、MTU路径发现,以及模拟各种网络攻击(如SYN Flood)。当我们测试的对象是四层负载均衡、防火墙策略,或者需要进行协议栈层面的压力测试时,hping3就派上用场了。请注意,使用hping3进行压力测试务必在你自己拥有完全控制权的目标上进行,绝对禁止对未经授权的任何网络目标发起测试,这不仅是道德问题,更可能触犯法律。
选择逻辑:对于绝大多数Web API和服务的压力测试,
curl(脚本化)和wrk的组合足以覆盖90%的场景。ab用于快速验证,hping3用于更底层的网络测试。本教程将以curl脚本化作为主线,因为它最能体现“搭建”和“定制”的过程,并穿插介绍wrk的用法。
2.2 系统监控与性能观测:vmstat、sar、iftop
发包机自身不能成为瓶颈,同时我们也需要监控目标服务器的状态。因此,监控工具必不可少。
vmstat和sar(System Activity Reporter):用于监控发包机本身的系统资源。vmstat可以实时查看CPU、内存、IO和系统进程的整体状态,快速判断系统负载。sar则更强大,它能收集、报告和保存系统活动信息,可以事后分析CPU利用率、内存使用、磁盘I/O、网络接口等历史数据。在长时间压测时,用sar记录数据尤为重要,它能帮你发现随着时间推移可能出现的资源耗尽问题(如内存泄漏导致压测力度下降)。iftop:类似于top命令,但是用于实时监控网络带宽。它可以显示当前服务器上各个网络连接的带宽使用情况,一目了然地看出压测脚本产生的实际网络流量是否达到了预期,以及是否有其他无关进程在占用带宽。
实操心得:在压测开始前,先运行vmstat 1或sar -u 1观察一下系统空闲状态,压测中对比观察。如果发包机的CPUus(用户态)或sy(系统态)利用率长时间接近100%,或者wa(IO等待)很高,说明发包机本身可能已经成为瓶颈,需要优化脚本或增加发包机节点(分布式压测)。
2.3 结果收集与简单分析:grep、awk、jq和ts
压测会产生大量输出,如何从中提取关键信息?
grep和awk:Shell文本处理的双子星。grep用于过滤出包含特定关键词(如real、failed)的行。awk则更强大,可以按列处理数据,例如计算所有请求耗时的平均值、最大值、成功率等。它们是Shell脚本中进行数据清洗和聚合的基石。jq:如果测试的API返回的是JSON格式,jq就是一个神器。它可以从JSON响应中快速提取某个字段的值,用于后续的断言或统计。ts(来自moreutils包):一个给每行输出添加时间戳的小工具。在并发压测时,日志是交错输出的,有了时间戳,你才能清晰地重建事件序列,对于排查“什么时候开始出错”至关重要。
工具安装命令:
# 对于基于Debian/Ubuntu的系统: sudo apt update sudo apt install -y curl apache2-utils wrk hping3 sysstat iftop jq moreutils # 对于基于RHEL/CentOS的系统: sudo yum install -y epel-release # 先安装EPEL仓库 sudo yum install -y curl httpd-tools wrk hping3 sysstat iftop jq moreutils安装后,sar可能需要启用才能开始记录历史数据:sudo systemctl enable sysstat && sudo systemctl start sysstat。
3. 基础Shell脚本编写:从单次请求到并发循环
理解了工具,我们开始用Shell脚本将它们串联起来。Shell脚本的魅力在于,它将简单的命令组合成强大的自动化流程。
3.1 脚本安全与基础规范
在写第一行代码前,有两个至关重要的习惯:
- 脚本头:每个Shell脚本开头都应该是
#!/bin/bash(指定解释器)和set -euo pipefail。-e使得脚本中任何命令失败就立即退出;-u遇到未定义的变量时报错;-o pipefail确保管道命令中任意一个环节失败,整个管道就视为失败。这能避免很多隐蔽的错误。 - 变量使用:对于可能包含空格或特殊字符的路径、URL,引用变量时务必用双引号,如
"$URL"。使用${VAR}形式也更清晰和安全。
一个标准的脚本模板如下:
#!/bin/bash set -euo pipefail # 定义变量 TARGET_URL="http://your-api-endpoint.com/test" REQUEST_COUNT=100 CONCURRENCY=10 LOG_FILE="pressure_test_$(date +%Y%m%d_%H%M%S).log" # 函数定义 function send_request() { local url=$1 curl -s -o /dev/null -w "%{http_code}\t%{time_total}\n" "$url" } # 主逻辑 echo "开始压测,目标: $TARGET_URL" | tee -a "$LOG_FILE" # ... 后续代码3.2 实现for循环与并发控制
for循环是发起多次请求的核心结构。
基础串行循环:
for ((i=1; i<=$REQUEST_COUNT; i++)); do echo "发起第 $i 次请求..." curl -s "http://目标地址" > /dev/null done这种方式一次只发一个请求,上一个结束才发下一个,主要用于功能验证,完全无法制造压力。
引入并发(后台任务):
for ((i=1; i<=$REQUEST_COUNT; i++)); do { curl -s -o /dev/null -w "请求$i: HTTP状态码:%{http_code}, 耗时:%{time_total}秒\n" "$TARGET_URL" } & done wait # 等待所有后台任务结束 echo "所有请求发送完毕。"将curl命令放在{ } &中,使其在子Shell中后台运行。这样所有请求几乎同时发起,实现了并发。wait命令会阻塞,直到所有后台进程都完成。这是最简单粗暴的并发方式,但并发数不可控(一次性启动所有任务),可能瞬间耗尽系统资源或导致目标服务拒绝连接。
使用命名管道(FIFO)和xargs控制并发度: 更优雅的方式是控制同时进行的任务数量。这里介绍一种利用命名管道和xargs的方法:
# 创建一个命名管道 FIFO_FILE="/tmp/$$.fifo" mkfifo $FIFO_FILE # 将文件描述符6绑定到该管道 exec 6<>$FIFO_FILE rm -f $FIFO_FILE # 向管道中放入N个“令牌”,N就是并发数 for ((i=1; i<=$CONCURRENCY; i++)); do echo >&6 done echo "开始压测,总请求数: $REQUEST_COUNT, 并发数: $CONCURRENCY" for ((i=1; i<=$REQUEST_COUNT; i++)); do # 从管道读取一个令牌(如果没有令牌,则阻塞在这里) read -u6 { # 执行请求 HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$TARGET_URL") TIME_TOTAL=$(curl -s -o /dev/null -w "%{time_total}" "$TARGET_URL") echo "请求$i: 状态码=$HTTP_CODE, 耗时=${TIME_TOTAL}s" # 请求完成后,将令牌放回管道 echo >&6 } & done wait # 等待所有后台进程 exec 6>&- # 关闭文件描述符6 echo "压测结束。"这个脚本的精妙之处在于,它通过管道里的“令牌”数量,精确控制了最多只有$CONCURRENCY个curl进程同时运行。当一个请求完成,它归还令牌,新的请求才能获取令牌开始执行。这是实现可控并发的一个经典模式。
使用GNU parallel或xargs -P: 对于更简单的控制,可以直接使用xargs的-P参数:
seq 1 $REQUEST_COUNT | xargs -n1 -P$CONCURRENCY -I{} curl -s -o /dev/null -w "请求{}: %{http_code}\n" "$TARGET_URL"seq生成数字序列,xargs的-P10表示最大10个进程并行,-I{}用数字替换{}。这种方式更简洁,但输出顺序可能会乱,且错误处理不如上面的令牌桶方式精细。
4. 构建一个完整的自动化压测脚本
我们将上面学到的知识整合,编写一个功能相对完整的压测脚本pressure_test.sh。这个脚本将包含参数化、并发控制、结果收集、简单统计和日志记录。
4.1 脚本架构与参数解析
一个好的脚本应该易于配置。我们使用命令行参数来传递关键配置。
#!/bin/bash set -euo pipefail # 默认参数 TARGET_URL="" REQUEST_COUNT=1000 CONCURRENCY=50 TIMEOUT=10 LOG_PREFIX="pressure_test" VERBOSE=false # 帮助信息 function show_usage() { cat << EOF 用法: $0 -u <目标URL> [-c 并发数] [-n 总请求数] [-t 超时秒数] [-l 日志前缀] [-v] 参数: -u, --url 目标URL (必需) -c, --concurrency 并发连接数 (默认: 50) -n, --requests 总请求数 (默认: 1000) -t, --timeout 单个请求超时时间(秒) (默认: 10) -l, --log-prefix 日志文件前缀 (默认: pressure_test) -v, --verbose 显示详细输出 -h, --help 显示此帮助信息 示例: $0 -u http://api.example.com/health -c 20 -n 5000 EOF exit 0 } # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -u|--url) TARGET_URL="$2" shift 2 ;; -c|--concurrency) CONCURRENCY="$2" shift 2 ;; -n|--requests) REQUEST_COUNT="$2" shift 2 ;; -t|--timeout) TIMEOUT="$2" shift 2 ;; -l|--log-prefix) LOG_PREFIX="$2" shift 2 ;; -v|--verbose) VERBOSE=true shift ;; -h|--help) show_usage ;; *) echo "错误: 未知参数 $1" show_usage exit 1 ;; esac done # 检查必需参数 if [[ -z "$TARGET_URL" ]]; then echo "错误: 必须指定目标URL (-u)" show_usage exit 1 fi # 创建日志目录和文件 LOG_DIR="./logs" mkdir -p "$LOG_DIR" TIMESTAMP=$(date +%Y%m%d_%H%M%S) LOG_FILE="${LOG_DIR}/${LOG_PREFIX}_${TIMESTAMP}.log" RESULT_FILE="${LOG_DIR}/${LOG_PREFIX}_${TIMESTAMP}_result.txt" echo "==========================================" | tee -a "$LOG_FILE" echo "压测开始时间: $(date)" | tee -a "$LOG_FILE" echo "目标URL: $TARGET_URL" | tee -a "$LOG_FILE" echo "总请求数: $REQUEST_COUNT" | tee -a "$LOG_FILE" echo "并发数: $CONCURRENCY" | tee -a "$LOG_FILE" echo "超时设置: ${TIMEOUT}s" | tee -a "$LOG_FILE" echo "==========================================" | tee -a "$LOG_FILE"4.2 核心压测引擎与结果收集
接下来是脚本的核心,使用“令牌桶”模型实现并发控制,并收集每个请求的结果。
# 初始化统计变量 TOTAL_SUCCESS=0 TOTAL_FAIL=0 declare -a RESPONSE_TIMES # 用于存储响应时间的数组(如果请求量巨大,需注意内存) # 创建命名管道用于并发控制 FIFO_FILE="/tmp/$$.fifo" mkfifo "$FIFO_FILE" exec 6<>"$FIFO_FILE" rm -f "$FIFO_FILE" # 初始化令牌 for ((i=0; i<CONCURRENCY; i++)); do echo >&6 done echo "启动压测引擎..." | tee -a "$LOG_FILE" START_TIME=$(date +%s.%N) # 主压测循环 for ((i=1; i<=REQUEST_COUNT; i++)); do read -u6 # 获取令牌 { # 使用curl发起请求,并捕获输出 # -s: 静默模式,不显示进度和错误信息 # -o /dev/null: 将响应体丢弃 # -w: 写入格式化输出:HTTP状态码,总时间,建立连接时间,开始传输时间 # --max-time: 超时设置 # --connect-timeout: 连接超时 CURL_OUTPUT=$(curl -s -o /dev/null \ -w "%{http_code}\t%{time_total}\t%{time_connect}\t%{time_starttransfer}\n" \ --max-time "$TIMEOUT" \ --connect-timeout 5 \ "$TARGET_URL" 2>&1) # 将标准错误也重定向到变量 CURL_EXIT_CODE=$? HTTP_CODE=$(echo "$CURL_OUTPUT" | awk '{print $1}') TIME_TOTAL=$(echo "$CURL_OUTPUT" | awk '{print $2}') TIME_CONNECT=$(echo "$CURL_OUTPUT" | awk '{print $3}') TIME_STARTTRANSFER=$(echo "$CURL_OUTPUT" | awk '{print $4}') # 判断请求成功与否 (假设2xx和3xx状态码为成功) if [[ $CURL_EXIT_CODE -eq 0 && $HTTP_CODE =~ ^[23][0-9][0-9]$ ]]; then STATUS="SUCCESS" ((TOTAL_SUCCESS++)) RESPONSE_TIMES+=("$TIME_TOTAL") # 记录成功请求的耗时 LOG_MSG="请求#$i: 状态码=$HTTP_CODE, 总耗时=${TIME_TOTAL}s, 连接耗时=${TIME_CONNECT}s, 传输开始耗时=${TIME_STARTTRANSFER}s" else STATUS="FAIL" ((TOTAL_FAIL++)) LOG_MSG="请求#$i: 失败! Curl退出码=$CURL_EXIT_CODE, HTTP状态码=$HTTP_CODE, 错误信息: $CURL_OUTPUT" fi # 输出日志 if [[ "$VERBOSE" == true || "$STATUS" == "FAIL" ]]; then echo "$LOG_MSG" | tee -a "$LOG_FILE" else echo "$LOG_MSG" >> "$LOG_FILE" fi echo >&6 # 归还令牌 } & done wait # 等待所有后台进程结束 END_TIME=$(date +%s.%N) exec 6>&- # 关闭文件描述符 # 计算总耗时 TOTAL_DURATION=$(echo "$END_TIME - $START_TIME" | bc) echo "所有请求完成,总耗时: ${TOTAL_DURATION} 秒" | tee -a "$LOG_FILE"4.3 结果统计与报告生成
压测结束后,我们需要对收集的数据进行统计分析。
# 计算成功率 SUCCESS_RATE=$(echo "scale=2; $TOTAL_SUCCESS * 100 / $REQUEST_COUNT" | bc) echo "============== 压测结果统计 ==============" | tee -a "$RESULT_FILE" echo "总请求数: $REQUEST_COUNT" | tee -a "$RESULT_FILE" echo "成功请求: $TOTAL_SUCCESS" | tee -a "$RESULT_FILE" echo "失败请求: $TOTAL_FAIL" | tee -a "$RESULT_FILE" echo "成功率: ${SUCCESS_RATE}%" | tee -a "$RESULT_FILE" echo "总耗时: ${TOTAL_DURATION} 秒" | tee -a "$RESULT_FILE" echo "平均QPS: $(echo "scale=2; $REQUEST_COUNT / $TOTAL_DURATION" | bc)" | tee -a "$RESULT_FILE" # 计算响应时间统计(仅统计成功的请求) if [[ ${#RESPONSE_TIMES[@]} -gt 0 ]]; then # 将时间数组排序,用于计算中位数和百分位数 IFS=$'\n' SORTED_TIMES=($(sort -n <<<"${RESPONSE_TIMES[*]}")) unset IFS COUNT=${#SORTED_TIMES[@]} # 计算平均值 SUM=0 for t in "${SORTED_TIMES[@]}"; do SUM=$(echo "$SUM + $t" | bc) done AVG_TIME=$(echo "scale=4; $SUM / $COUNT" | bc) # 计算中位数 (50%) MID=$((COUNT / 2)) if (( COUNT % 2 == 0 )); then MEDIAN=$(echo "scale=4; (${SORTED_TIMES[MID-1]} + ${SORTED_TIMES[MID]}) / 2" | bc) else MEDIAN=${SORTED_TIMES[MID]} fi # 计算P90(第90百分位数) P90_INDEX=$(( (COUNT * 90 + 99) / 100 - 1 )) # 向上取整的索引计算 P90=${SORTED_TIMES[$P90_INDEX]} # 计算P99(第99百分位数) P99_INDEX=$(( (COUNT * 99 + 99) / 100 - 1 )) P99=${SORTED_TIMES[$P99_INDEX]} # 计算最小值和最大值 MIN_TIME=${SORTED_TIMES[0]} MAX_TIME=${SORTED_TIMES[COUNT-1]} echo "============== 响应时间分析(单位:秒) ==============" | tee -a "$RESULT_FILE" echo "样本数(成功请求): $COUNT" | tee -a "$RESULT_FILE" echo "平均响应时间: $AVG_TIME" | tee -a "$RESULT_FILE" echo "最小响应时间: $MIN_TIME" | tee -a "$RESULT_FILE" echo "最大响应时间: $MAX_TIME" | tee -a "$RESULT_FILE" echo "中位数响应时间: $MEDIAN" | tee -a "$RESULT_FILE" echo "P90响应时间: $P90" | tee -a "$RESULT_FILE" echo "P99响应时间: $P99" | tee -a "$RESULT_FILE" else echo "警告:无成功请求,无法计算响应时间统计。" | tee -a "$RESULT_FILE" fi echo "==========================================" | tee -a "$RESULT_FILE" echo "详细日志请查看: $LOG_FILE" | tee -a "$RESULT_FILE" echo "本报告保存至: $RESULT_FILE" | tee -a "$RESULT_FILE" echo "压测结束时间: $(date)" | tee -a "$LOG_FILE"现在,你已经拥有了一个功能完整的压测脚本。通过命令行调用它:
# 基本用法 bash pressure_test.sh -u http://192.168.1.100:8080/api/v1/test # 高级用法:5000个请求,100并发,超时15秒,并显示详细日志 bash pressure_test.sh -u http://your-service.com/endpoint -n 5000 -c 100 -t 15 -v -l my_test脚本会自动创建logs目录,并在其中生成带有时间戳的详细日志文件和结果摘要文件。
5. 高级技巧与场景扩展
基础脚本能满足大部分需求,但在复杂场景下,我们还需要更多技巧。
5.1 使用wrk进行高性能HTTP压测
当需要产生极高的HTTP压力时,用Shell启动成千上万个curl进程开销太大。此时wrk是更好的选择。它用C语言编写,基于多线程和事件循环,效率极高。
一个简单的wrk命令如下:
wrk -t12 -c400 -d30s --latency http://目标地址-t12: 使用12个线程。-c400: 保持400个HTTP连接打开。-d30s: 持续压测30秒。--latency: 输出详细的延迟分布统计。
但wrk的真正威力在于Lua脚本。你可以编写test.lua脚本:
-- 初始化阶段 function init(args) -- 可以在这里读取外部参数或初始化变量 math.randomseed(os.time()) end -- 每个线程初始化 function thread_init(thread_id) -- 每个线程独立的初始化,比如分配不同的用户ID池 end -- 请求生成函数 request = function() -- 动态生成路径或参数 local uid = math.random(1000, 9999) local path = "/api/user/" .. uid -- 可以自定义请求头、方法、Body local headers = {} headers["Content-Type"] = "application/json" headers["Authorization"] = "Bearer fake_token" local body = string.format('{"query": "user_%d"}', uid) return wrk.format("POST", path, headers, body) end -- 响应处理函数(可选) function response(status, headers, body) -- 可以在这里检查响应状态或内容,进行自定义统计 if status ~= 200 then print("非200响应: " .. status) end end然后运行:
wrk -t12 -c400 -d60s -s test.lua --latency http://目标地址通过Lua脚本,你可以轻松实现参数化、混合场景(不同接口按比例调用)、请求依赖(如先登录获取token)等复杂逻辑。你可以将wrk命令集成到你的Shell脚本中,用于执行特定场景的压测。
5.2 模拟混合业务场景
真实的用户流量不是单一的。我们可以编写一个调度脚本,混合多种请求模式。
#!/bin/bash # mixed_scenario.sh API_BASE="http://your-service.com" # 定义不同的API端点及其权重(百分比) declare -A APIS=( ["/api/v1/get_info"]=60 # 60% 的请求是查询信息 ["/api/v1/submit"]=30 # 30% 的请求是提交数据 ["/api/v1/health"]=10 # 10% 的请求是健康检查 ) # 根据权重生成请求列表(这里简化,实际可按概率随机选择) REQUEST_LIST=() for api in "${!APIS[@]}"; do weight=${APIS[$api]} # 根据权重,将API端点重复加入列表(这是一种简单实现) for ((i=0; i<weight; i++)); do REQUEST_LIST+=("$API_BASE$api") done done TOTAL_REQUESTS=1000 CONCURRENCY=20 # 使用之前的令牌桶并发模型 FIFO_FILE="/tmp/mixed_$$.fifo" mkfifo "$FIFO_FILE" exec 6<>"$FIFO_FILE" rm -f "$FIFO_FILE" for ((i=0; i<CONCURRENCY; i++)); do echo >&6; done for ((i=1; i<=TOTAL_REQUESTS; i++)); do read -u6 { # 从请求列表中随机选取一个URL LIST_SIZE=${#REQUEST_LIST[@]} RANDOM_INDEX=$(( RANDOM % LIST_SIZE )) TARGET_URL="${REQUEST_LIST[$RANDOM_INDEX]}" # 对于提交请求,可以随机生成一些JSON数据 if [[ "$TARGET_URL" == *"/submit" ]]; then DATA=$(printf '{"id": %d, "value": "test_%d"}' $RANDOM $RANDOM) curl -X POST -H "Content-Type: application/json" -d "$DATA" -s -o /dev/null -w "%{http_code}\n" "$TARGET_URL" else curl -s -o /dev/null -w "%{http_code}\n" "$TARGET_URL" fi echo >&6 } & done wait exec 6>&- echo "混合场景压测完成。"这个脚本模拟了不同接口按权重分布的访问模式,更贴近真实业务流量。
5.3 分布式压测与协调
单台发包机可能有性能瓶颈或网络限制。要产生更大压力,需要多台机器协同工作。思路是:一台控制机(Master)和多个压测机(Agent)。
控制机脚本 (master.sh):
#!/bin/bash # master.sh - 分发任务并汇总结果 AGENTS=("agent1_ip" "agent2_ip" "agent3_ip") SCRIPT="pressure_test.sh" PARAMS="-u http://target.com -n 2000 -c 50" RESULT_DIR="./aggregated_results" mkdir -p "$RESULT_DIR" for AGENT in "${AGENTS[@]}"; do echo "在 $AGENT 上启动压测..." # 假设使用ssh密钥免密登录,将脚本和参数传到Agent并执行 scp "$SCRIPT" "$AGENT:/tmp/" ssh "$AGENT" "bash /tmp/$SCRIPT $PARAMS -l ${AGENT}_test" & # 将后台进程ID记录下来,方便后续等待 PIDS+=($!) done echo "等待所有Agent压测完成..." for PID in "${PIDS[@]}"; do wait $PID done # 从各Agent收集结果日志 for AGENT in "${AGENTS[@]}"; do scp "$AGENT:./logs/${AGENT}_test_*.log" "$RESULT_DIR/" 2>/dev/null || true scp "$AGENT:./logs/${AGENT}_test_*_result.txt" "$RESULT_DIR/" 2>/dev/null || true done echo "所有Agent压测完成,日志已收集至 $RESULT_DIR" # 可以在这里添加一个汇总分析所有日志的脚本注意事项:分布式压测需要解决时钟同步、结果去重、全局监控汇总等问题。更成熟的方案可以考虑使用Ansible进行批量部署和执行,或者使用专门的分布式压测框架如Tsung。
6. 常见问题、性能调优与安全警示
在实际操作中,你会遇到各种问题。这里记录一些典型的坑和解决方案。
6.1 性能瓶颈排查清单
当压测达不到预期压力时,按以下顺序排查:
发包机自身资源:
- CPU:运行
top或htop,看%Cpu(s)行。如果us(用户态)或sy(系统态)长期高于80%,说明CPU是瓶颈。对于Shell脚本并发大量进程,系统调用开销(sy)可能很高。考虑减少并发数,或换用wrk这类更高效的工具。 - 内存:通常不是主要瓶颈,除非脚本有内存泄漏。用
free -h查看。 - 网络:运行
iftop或nload,查看出口带宽是否已打满。千兆网卡的理论上限约125MB/s。如果打满,考虑增加发包机或使用万兆网络。 - 文件描述符:每个网络连接、每个进程都会消耗文件描述符。用
ulimit -n查看单进程限制,用cat /proc/sys/fs/file-nr查看系统已用和总数。如果不够,需要修改限制:ulimit -n 65535(临时)或在/etc/security/limits.conf中永久修改。
- CPU:运行
目标服务器状态:
- 通过监控工具(如目标服务器的
vmstat,iostat,netstat)观察其CPU、内存、磁盘IO和网络连接数。如果目标服务器的资源使用率很低,但你的压测QPS上不去,问题可能出在网络链路上或客户端配置上。 - 检查目标服务的日志,看是否有大量错误(如连接拒绝、超时)。
- 通过监控工具(如目标服务器的
网络链路问题:
- 延迟和丢包:用
ping和mtr(或traceroute)检查到目标服务器的网络质量。高延迟或丢包会严重限制有效吞吐量。 - 连接数限制:检查目标服务器的防火墙、负载均衡器或服务本身(如Nginx的
worker_connections,Linux的net.core.somaxconn)是否有连接数限制。
- 延迟和丢包:用
6.2 脚本执行中的典型错误与处理
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
bash: ./test.sh: Permission denied | 脚本没有执行权限 | chmod +x test.sh |
-bash: ./test.sh: /bin/bash^M: bad interpreter | 脚本在Windows下编辑,行尾是CRLF | 使用dos2unix test.sh转换,或用sed -i 's/\r$//' test.sh |
curl: (7) Failed to connect to host port ... | 网络不通、目标服务未启动、防火墙阻止 | 检查网络连通性(telnet host port)、服务状态和防火墙规则 |
curl: (28) Operation timed out after ... | 连接超时或服务器响应太慢 | 增加--connect-timeout和--max-time参数,或检查服务器负载 |
| 脚本启动大量进程后系统变卡甚至无响应 | 并发进程数过多,耗尽系统资源 | 使用“令牌桶”或xargs -P严格控制并发数,或换用wrk |
| 压测一段时间后成功率骤降 | 目标服务器或中间件连接池耗尽、内存泄漏 | 降低压测强度,观察服务器监控指标,检查服务日志 |
too many open files | 系统或进程打开文件数超限 | 使用ulimit -n查看并修改限制,如ulimit -n 65535 |
6.3 至关重要的安全与法律警示
这是搭建和使用发包机必须时刻牢记的底线。
警告:压力测试是一把双刃剑。不当使用等同于网络攻击。
明确授权:只对你拥有完全所有权和控制权的目标进行测试。这包括:
- 你自己开发并部署在本地或私有云的服务。
- 公司内部明确授权你进行压测的预发布或生产环境(必须有书面或邮件授权)。
- 第三方提供的、明确允许进行压力测试的公开服务(如某些云服务商提供的测试端点)。
绝对禁止:
- 严禁对任何互联网上未经明确授权的网站、API、服务进行压力测试。这属于“拒绝服务(DoS)”攻击的范畴,是违法行为。
- 严禁使用本教程中的方法进行任何形式的“抢票”、“刷单”、“秒杀”脚本编写或对商业网站进行恶意请求。这不仅不道德,还可能涉及计算机信息系统犯罪。
- 严禁在脚本中尝试绕过任何安全机制(如验证码、频率限制)。
伦理与最佳实践:
- 控制范围:在测试前,明确测试时间窗口,并通知可能受影响的相关方(如运维、其他开发人员)。
- 循序渐进:不要一开始就上最大压力。从一个很低的并发数开始,逐步增加,观察系统表现。这有助于你发现性能拐点,而不是直接冲垮服务。
- 监控与止损:压测时,密切监控目标系统的核心指标(CPU、内存、响应时间、错误率)。一旦发现服务不可用或核心业务受损,立即停止压测。
- 清理现场:测试结束后,确保脚本完全停止,没有残留进程在后台继续发送请求。
搭建发包机、编写自动化脚本是一项强大的技能,它能帮助你更好地理解系统行为,构建更健壮的服务。但请务必带着责任感和法律意识来使用这项技能,让它成为保障系统稳定的工具,而非破坏秩序的武器。
本文还有配套的精品资源,点击获取