refactor: 整理项目根目录结构
整理内容: - 删除 60+ 临时测试输出文件 (*.txt) - 移动二进制文件到 bin/ 目录 - 移动 Shell 脚本到 scripts/ 目录 - scripts/dev/: check_gitea.sh, check_sub2api.sh, run_tests.sh - scripts/deploy/: deploy_*.sh, simple_deploy.sh - scripts/ops/: fix_nginx.sh, fix_ssl.sh, install_docker.sh - scripts/test/: test_*.sh, test_*.bat - 移动批处理文件到 scripts/ - 移动 Python 脚本到 tools/ - 清理临时日志文件 保留根目录必要文件: - go.mod, go.sum, go.work - Makefile, docker-compose.yml - .env.example, .gitignore - README.md, AGENTS.md, DEPLOY_GUIDE.md 验证: go build ./... && go test ./... 通过
This commit is contained in:
124
scripts/chaos/ce-001-database-unavailable.ps1
Normal file
124
scripts/chaos/ce-001-database-unavailable.ps1
Normal file
@@ -0,0 +1,124 @@
|
||||
# CE-001: 数据库不可用韧性验证
|
||||
# 验证:当数据库连接中断时,健康检查正确返回 DOWN,API 返回 503
|
||||
|
||||
param(
|
||||
[string]$BaseURL = "http://localhost:8080",
|
||||
[string]$DBPath = ".\data\user_management.db",
|
||||
[int]$TimeoutSeconds = 30
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$passed = 0
|
||||
$failed = 0
|
||||
|
||||
function Write-Pass { param($msg) Write-Host " ✅ $msg" -ForegroundColor Green; $script:passed++ }
|
||||
function Write-Fail { param($msg) Write-Host " ❌ $msg" -ForegroundColor Red; $script:failed++ }
|
||||
function Write-Step { param($msg) Write-Host "`n[STEP] $msg" -ForegroundColor Cyan }
|
||||
|
||||
Write-Host "=== CE-001: 数据库不可用韧性验证 ===" -ForegroundColor Magenta
|
||||
Write-Host "目标服务: $BaseURL"
|
||||
Write-Host "数据库路径: $DBPath"
|
||||
Write-Host ""
|
||||
|
||||
# 前置检查:服务必须正常运行
|
||||
Write-Step "前置检查:验证服务初始状态"
|
||||
try {
|
||||
$health = Invoke-RestMethod -Uri "$BaseURL/health/ready" -TimeoutSec 5
|
||||
if ($health.status -eq "UP") {
|
||||
Write-Pass "服务初始状态 UP"
|
||||
} else {
|
||||
Write-Fail "服务初始状态不健康 ($($health.status)),请先启动服务"
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Fail "无法连接到服务: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 记录实验前指标
|
||||
Write-Step "记录实验前基线指标"
|
||||
try {
|
||||
$beforeMetrics = Invoke-RestMethod -Uri "$BaseURL/metrics" -TimeoutSec 5
|
||||
Write-Pass "基线指标已记录"
|
||||
} catch {
|
||||
Write-Host " ℹ️ /metrics 端点未就绪(可能是 P0 修复前状态),跳过指标记录"
|
||||
}
|
||||
|
||||
# 注意:此脚本为"观察模式",不实际关闭数据库
|
||||
# 在生产混沌实验中,应使用专用的故障注入工具
|
||||
Write-Step "故障注入模拟(观察模式)"
|
||||
Write-Host " ℹ️ 本实验为观察模式,不实际关闭数据库" -ForegroundColor Yellow
|
||||
Write-Host " ℹ️ 生产环境请使用: chaostoolkit / Gremlin / 手动关闭 DB 进程"
|
||||
|
||||
# 模拟:快速连续请求,观察健康检查行为
|
||||
Write-Step "并发健康检查验证"
|
||||
$jobs = 1..5 | ForEach-Object {
|
||||
Start-Job -ScriptBlock {
|
||||
param($url)
|
||||
try {
|
||||
$resp = Invoke-RestMethod -Uri "$url/health/ready" -TimeoutSec 3
|
||||
return @{ status = $resp.status; ok = $true }
|
||||
} catch {
|
||||
return @{ status = "ERROR"; ok = $false; error = $_.Exception.Message }
|
||||
}
|
||||
} -ArgumentList $BaseURL
|
||||
}
|
||||
|
||||
$results = $jobs | Wait-Job | Receive-Job
|
||||
$allUp = ($results | Where-Object { $_.status -ne "UP" }).Count -eq 0
|
||||
|
||||
if ($allUp) {
|
||||
Write-Pass "5次并发健康检查全部返回 UP"
|
||||
} else {
|
||||
Write-Fail "部分健康检查失败: $($results | ConvertTo-Json -Compress)"
|
||||
}
|
||||
|
||||
# 验证健康检查格式是否符合规范
|
||||
Write-Step "验证健康检查响应格式"
|
||||
try {
|
||||
$health = Invoke-RestMethod -Uri "$BaseURL/health/ready" -TimeoutSec 5
|
||||
|
||||
if ($health.status) { Write-Pass "包含 status 字段: $($health.status)" }
|
||||
else { Write-Fail "缺少 status 字段" }
|
||||
|
||||
if ($health.checks) { Write-Pass "包含 checks 字段" }
|
||||
else { Write-Fail "缺少 checks 字段" }
|
||||
|
||||
if ($health.timestamp) { Write-Pass "包含 timestamp 字段: $($health.timestamp)" }
|
||||
else { Write-Fail "缺少 timestamp 字段(需要升级 health.go)" }
|
||||
|
||||
if ($health.checks.database) {
|
||||
Write-Pass "database 检查存在: $($health.checks.database.status)"
|
||||
} else {
|
||||
Write-Fail "缺少 database 检查"
|
||||
}
|
||||
} catch {
|
||||
Write-Fail "健康检查请求失败: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 验证 Liveness 端点(应始终返回成功)
|
||||
Write-Step "验证 Liveness 端点(应始终成功)"
|
||||
try {
|
||||
$resp = Invoke-WebRequest -Uri "$BaseURL/health/live" -TimeoutSec 5
|
||||
if ($resp.StatusCode -eq 200 -or $resp.StatusCode -eq 204) {
|
||||
Write-Pass "Liveness 检查返回 $($resp.StatusCode)"
|
||||
} else {
|
||||
Write-Fail "Liveness 检查返回非成功状态: $($resp.StatusCode)"
|
||||
}
|
||||
} catch {
|
||||
Write-Fail "Liveness 检查失败: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# 汇总
|
||||
Write-Host "`n=== 实验结果 ===" -ForegroundColor Magenta
|
||||
Write-Host "通过: $passed"
|
||||
Write-Host "失败: $failed"
|
||||
|
||||
if ($failed -eq 0) {
|
||||
Write-Host "`n✅ CE-001 观察阶段通过" -ForegroundColor Green
|
||||
Write-Host "⚠️ 完整实验需要手动关闭数据库并验证 503 响应" -ForegroundColor Yellow
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "`n❌ CE-001 存在 $failed 个验证失败" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
172
scripts/chaos/ce-005-concurrent-login.ps1
Normal file
172
scripts/chaos/ce-005-concurrent-login.ps1
Normal file
@@ -0,0 +1,172 @@
|
||||
# CE-005: 并发登录压测 & 速率限制验证
|
||||
# 验证:高并发下速率限制(Rate Limiting)是否正常工作
|
||||
|
||||
param(
|
||||
[string]$BaseURL = "http://localhost:8080",
|
||||
[int]$Concurrency = 20,
|
||||
[int]$Duration = 15,
|
||||
[switch]$Verbose
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
Write-Host "=== CE-005: 并发登录压测 & 速率限制验证 ===" -ForegroundColor Magenta
|
||||
Write-Host "目标服务: $BaseURL"
|
||||
Write-Host "并发协程: $Concurrency"
|
||||
Write-Host "持续时间: ${Duration}s"
|
||||
Write-Host ""
|
||||
|
||||
# 前置检查
|
||||
Write-Host "[前置检查] 服务健康状态..." -ForegroundColor Cyan
|
||||
try {
|
||||
$health = Invoke-RestMethod -Uri "$BaseURL/health/ready" -TimeoutSec 5
|
||||
if ($health.status -ne "UP") {
|
||||
Write-Host "❌ 服务不健康,终止实验" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host " ✅ 服务状态: UP" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " ❌ 无法连接到服务: $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 并发压测
|
||||
Write-Host "`n[压测中] 启动 $Concurrency 个并发协程,持续 ${Duration}s..." -ForegroundColor Cyan
|
||||
|
||||
$startTime = Get-Date
|
||||
$jobs = 1..$Concurrency | ForEach-Object {
|
||||
$workerID = $_
|
||||
Start-Job -ScriptBlock {
|
||||
param($BaseURL, $Duration, $workerID)
|
||||
|
||||
$end = (Get-Date).AddSeconds($Duration)
|
||||
$results = @{
|
||||
total = 0
|
||||
http_200 = 0
|
||||
http_400 = 0
|
||||
http_401 = 0
|
||||
http_429 = 0
|
||||
http_500 = 0
|
||||
other = 0
|
||||
errors = 0
|
||||
}
|
||||
|
||||
while ((Get-Date) -lt $end) {
|
||||
try {
|
||||
$body = @{
|
||||
account = "chaos_test_user_$workerID"
|
||||
password = "wrong_password_chaos_test"
|
||||
} | ConvertTo-Json
|
||||
|
||||
$resp = Invoke-WebRequest `
|
||||
-Uri "$BaseURL/api/v1/auth/login" `
|
||||
-Method POST `
|
||||
-Body $body `
|
||||
-ContentType "application/json" `
|
||||
-ErrorAction SilentlyContinue `
|
||||
-TimeoutSec 5
|
||||
|
||||
$results.total++
|
||||
switch ($resp.StatusCode) {
|
||||
200 { $results.http_200++ }
|
||||
400 { $results.http_400++ }
|
||||
401 { $results.http_401++ }
|
||||
429 { $results.http_429++ }
|
||||
500 { $results.http_500++ }
|
||||
default { $results.other++ }
|
||||
}
|
||||
} catch {
|
||||
$results.total++
|
||||
$results.errors++
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds 50
|
||||
}
|
||||
|
||||
return $results
|
||||
} -ArgumentList $BaseURL, $Duration, $workerID
|
||||
}
|
||||
|
||||
Write-Host " ⏳ 等待实验完成..." -ForegroundColor Yellow
|
||||
$jobs | Wait-Job | Out-Null
|
||||
|
||||
$elapsed = (Get-Date) - $startTime
|
||||
Write-Host " ✅ 实验完成,耗时: $([math]::Round($elapsed.TotalSeconds, 1))s" -ForegroundColor Green
|
||||
|
||||
# 汇总结果
|
||||
$totals = @{
|
||||
total = 0; http_200 = 0; http_400 = 0; http_401 = 0
|
||||
http_429 = 0; http_500 = 0; other = 0; errors = 0
|
||||
}
|
||||
|
||||
$jobs | Receive-Job | ForEach-Object {
|
||||
$r = $_
|
||||
$totals.total += $r.total
|
||||
$totals.http_200 += $r.http_200
|
||||
$totals.http_400 += $r.http_400
|
||||
$totals.http_401 += $r.http_401
|
||||
$totals.http_429 += $r.http_429
|
||||
$totals.http_500 += $r.http_500
|
||||
$totals.other += $r.other
|
||||
$totals.errors += $r.errors
|
||||
}
|
||||
$jobs | Remove-Job
|
||||
|
||||
# 显示结果
|
||||
$rateTotal = [math]::Max($totals.total, 1)
|
||||
Write-Host "`n=== 压测结果 ===" -ForegroundColor Magenta
|
||||
Write-Host "总请求数: $($totals.total)"
|
||||
Write-Host "吞吐量: $([math]::Round($totals.total / $elapsed.TotalSeconds, 1)) req/s"
|
||||
Write-Host ""
|
||||
Write-Host "HTTP 响应分布:"
|
||||
Write-Host " 200 成功: $($totals.http_200) ($([math]::Round($totals.http_200 / $rateTotal * 100, 1))%)"
|
||||
Write-Host " 400 请求错误: $($totals.http_400) ($([math]::Round($totals.http_400 / $rateTotal * 100, 1))%)"
|
||||
Write-Host " 401 认证失败: $($totals.http_401) ($([math]::Round($totals.http_401 / $rateTotal * 100, 1))%)"
|
||||
Write-Host " 429 速率限制: $($totals.http_429) ($([math]::Round($totals.http_429 / $rateTotal * 100, 1))%)" -ForegroundColor Yellow
|
||||
Write-Host " 500 服务错误: $($totals.http_500) ($([math]::Round($totals.http_500 / $rateTotal * 100, 1))%)" -ForegroundColor $(if ($totals.http_500 -gt 0) {"Red"} else {"White"})
|
||||
Write-Host " 其他/错误: $($totals.other + $totals.errors)"
|
||||
|
||||
# 验证
|
||||
Write-Host "`n=== 验证 ===" -ForegroundColor Cyan
|
||||
|
||||
$passed = 0; $failed = 0
|
||||
|
||||
# 验证1:速率限制触发
|
||||
if ($totals.http_429 -gt 0) {
|
||||
Write-Host " ✅ 速率限制已触发 ($($totals.http_429) 次 429 响应)" -ForegroundColor Green
|
||||
$passed++
|
||||
} else {
|
||||
Write-Host " ❌ 速率限制未触发(0 次 429),请检查 config.yaml ratelimit 配置" -ForegroundColor Red
|
||||
$failed++
|
||||
}
|
||||
|
||||
# 验证2:无服务器错误(500 不应出现)
|
||||
if ($totals.http_500 -eq 0) {
|
||||
Write-Host " ✅ 无 5xx 错误,服务稳定" -ForegroundColor Green
|
||||
$passed++
|
||||
} else {
|
||||
Write-Host " ❌ 出现 $($totals.http_500) 次 5xx 错误,存在系统稳定性问题" -ForegroundColor Red
|
||||
$failed++
|
||||
}
|
||||
|
||||
# 验证3:QPS 合理(服务未被压垮)
|
||||
$targetQPS = $Concurrency * 5 # 理论最大 QPS
|
||||
$actualQPS = $totals.total / $elapsed.TotalSeconds
|
||||
if ($actualQPS -gt 0) {
|
||||
Write-Host " ✅ 服务保持响应,实际 QPS: $([math]::Round($actualQPS, 1))" -ForegroundColor Green
|
||||
$passed++
|
||||
} else {
|
||||
Write-Host " ❌ 服务可能已无响应" -ForegroundColor Red
|
||||
$failed++
|
||||
}
|
||||
|
||||
Write-Host "`n=== 实验总结 ===" -ForegroundColor Magenta
|
||||
Write-Host "通过: $passed 失败: $failed"
|
||||
|
||||
if ($failed -eq 0) {
|
||||
Write-Host "`n✅ CE-005 通过 — 速率限制在高并发下正常工作" -ForegroundColor Green
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "`n❌ CE-005 失败 — 存在 $failed 个验证问题" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user