Files
hfb_sys/scripts/stress_test.go
T
2026-06-05 22:01:14 +08:00

408 lines
11 KiB
Go

package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"math/rand"
"net/http"
"sync"
"sync/atomic"
"time"
)
// 压力测试工具 - 模拟实际业务场景
type TestConfig struct {
BaseURL string
Concurrency int
Duration time.Duration
Scenario string
}
type TestResult struct {
TotalRequests int64
SuccessRequests int64
FailedRequests int64
TotalLatency int64 // 毫秒
MinLatency int64
MaxLatency int64
Errors map[string]int64
}
var (
baseURL = flag.String("url", "http://localhost:8080", "API 基础地址")
concurrency = flag.Int("c", 10, "并发数")
duration = flag.Int("d", 60, "测试时长(秒)")
scenario = flag.String("s", "mixed", "测试场景: list_listings, create_order, chat, wallet, mixed")
)
func main() {
flag.Parse()
config := TestConfig{
BaseURL: *baseURL,
Concurrency: *concurrency,
Duration: time.Duration(*duration) * time.Second,
Scenario: *scenario,
}
fmt.Printf("=== 压力测试配置 ===\n")
fmt.Printf("目标地址: %s\n", config.BaseURL)
fmt.Printf("并发数: %d\n", config.Concurrency)
fmt.Printf("测试时长: %d 秒\n", *duration)
fmt.Printf("测试场景: %s\n", config.Scenario)
fmt.Printf("==================\n\n")
result := runTest(config)
printResult(result)
}
func runTest(config TestConfig) *TestResult {
result := &TestResult{
Errors: make(map[string]int64),
MinLatency: int64(^uint64(0) >> 1), // Max int64
}
var wg sync.WaitGroup
stopChan := make(chan struct{})
// 启动并发workers
for i := 0; i < config.Concurrency; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
worker(workerID, config, result, stopChan)
}(i)
}
// 等待测试时长
time.Sleep(config.Duration)
close(stopChan)
wg.Wait()
return result
}
func worker(id int, config TestConfig, result *TestResult, stopChan chan struct{}) {
client := &http.Client{
Timeout: 10 * time.Second,
}
for {
select {
case <-stopChan:
return
default:
executeScenario(client, config, result)
}
}
}
func executeScenario(client *http.Client, config TestConfig, result *TestResult) {
switch config.Scenario {
case "list_listings":
testListListings(client, config.BaseURL, result)
case "create_order":
testCreateOrder(client, config.BaseURL, result)
case "chat":
testChatMessages(client, config.BaseURL, result)
case "wallet":
testWalletLedger(client, config.BaseURL, result)
case "mixed":
// 混合场景:按实际业务比例分配
r := rand.Intn(100)
switch {
case r < 40: // 40% 查询商品列表
testListListings(client, config.BaseURL, result)
case r < 60: // 20% 查询订单
testListOrders(client, config.BaseURL, result)
case r < 75: // 15% 查询钱包流水
testWalletLedger(client, config.BaseURL, result)
case r < 85: // 10% 聊天消息
testChatMessages(client, config.BaseURL, result)
case r < 95: // 10% 创建订单
testCreateOrder(client, config.BaseURL, result)
default: // 5% 支付
testPayOrder(client, config.BaseURL, result)
}
default:
testHealthCheck(client, config.BaseURL, result)
}
}
// ============================================
// 测试场景实现
// ============================================
func testHealthCheck(client *http.Client, baseURL string, result *TestResult) {
start := time.Now()
resp, err := client.Get(baseURL + "/health")
latency := time.Since(start).Milliseconds()
atomic.AddInt64(&result.TotalRequests, 1)
updateLatency(result, latency)
if err != nil {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, "health_check_error: "+err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
atomic.AddInt64(&result.SuccessRequests, 1)
} else {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, fmt.Sprintf("health_check_status_%d", resp.StatusCode))
}
}
func testListListings(client *http.Client, baseURL string, result *TestResult) {
// 模拟不同的查询条件
page := rand.Intn(10) + 1
pageSize := []int{10, 20, 50}[rand.Intn(3)]
url := fmt.Sprintf("%s/api/listings?page=%d&page_size=%d", baseURL, page, pageSize)
start := time.Now()
resp, err := client.Get(url)
latency := time.Since(start).Milliseconds()
atomic.AddInt64(&result.TotalRequests, 1)
updateLatency(result, latency)
if err != nil {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, "list_listings_error: "+err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
atomic.AddInt64(&result.SuccessRequests, 1)
} else {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, fmt.Sprintf("list_listings_status_%d", resp.StatusCode))
}
}
func testListOrders(client *http.Client, baseURL string, result *TestResult) {
// 需要登录token,这里模拟匿名访问(会返回401)
page := rand.Intn(5) + 1
url := fmt.Sprintf("%s/api/orders?page=%d&page_size=20", baseURL, page)
start := time.Now()
resp, err := client.Get(url)
latency := time.Since(start).Milliseconds()
atomic.AddInt64(&result.TotalRequests, 1)
updateLatency(result, latency)
if err != nil {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, "list_orders_error: "+err.Error())
return
}
defer resp.Body.Close()
// 401是预期的(未登录)
if resp.StatusCode == 200 || resp.StatusCode == 401 {
atomic.AddInt64(&result.SuccessRequests, 1)
} else {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, fmt.Sprintf("list_orders_status_%d", resp.StatusCode))
}
}
func testCreateOrder(client *http.Client, baseURL string, result *TestResult) {
// 模拟创建订单(需要登录,会返回401)
listingID := rand.Intn(1000) + 1
payload := map[string]interface{}{
"listing_id": listingID,
"estimated_duration_hours": 24,
}
body, _ := json.Marshal(payload)
start := time.Now()
resp, err := client.Post(
baseURL+"/api/orders",
"application/json",
bytes.NewBuffer(body),
)
latency := time.Since(start).Milliseconds()
atomic.AddInt64(&result.TotalRequests, 1)
updateLatency(result, latency)
if err != nil {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, "create_order_error: "+err.Error())
return
}
defer resp.Body.Close()
// 401是预期的(未登录)
if resp.StatusCode == 200 || resp.StatusCode == 401 {
atomic.AddInt64(&result.SuccessRequests, 1)
} else {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, fmt.Sprintf("create_order_status_%d", resp.StatusCode))
}
}
func testPayOrder(client *http.Client, baseURL string, result *TestResult) {
orderID := rand.Intn(1000) + 1
url := fmt.Sprintf("%s/api/orders/%d/pay", baseURL, orderID)
payload := map[string]interface{}{
"provider": "mock",
}
body, _ := json.Marshal(payload)
start := time.Now()
resp, err := client.Post(url, "application/json", bytes.NewBuffer(body))
latency := time.Since(start).Milliseconds()
atomic.AddInt64(&result.TotalRequests, 1)
updateLatency(result, latency)
if err != nil {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, "pay_order_error: "+err.Error())
return
}
defer resp.Body.Close()
// 401是预期的(未登录)
if resp.StatusCode == 200 || resp.StatusCode == 401 {
atomic.AddInt64(&result.SuccessRequests, 1)
} else {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, fmt.Sprintf("pay_order_status_%d", resp.StatusCode))
}
}
func testWalletLedger(client *http.Client, baseURL string, result *TestResult) {
page := rand.Intn(10) + 1
url := fmt.Sprintf("%s/api/wallet/ledger?page=%d&page_size=20", baseURL, page)
start := time.Now()
resp, err := client.Get(url)
latency := time.Since(start).Milliseconds()
atomic.AddInt64(&result.TotalRequests, 1)
updateLatency(result, latency)
if err != nil {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, "wallet_ledger_error: "+err.Error())
return
}
defer resp.Body.Close()
// 401是预期的(未登录)
if resp.StatusCode == 200 || resp.StatusCode == 401 {
atomic.AddInt64(&result.SuccessRequests, 1)
} else {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, fmt.Sprintf("wallet_ledger_status_%d", resp.StatusCode))
}
}
func testChatMessages(client *http.Client, baseURL string, result *TestResult) {
conversationID := rand.Intn(100) + 1
url := fmt.Sprintf("%s/api/chats/%d/messages?page=1&page_size=50", baseURL, conversationID)
start := time.Now()
resp, err := client.Get(url)
latency := time.Since(start).Milliseconds()
atomic.AddInt64(&result.TotalRequests, 1)
updateLatency(result, latency)
if err != nil {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, "chat_messages_error: "+err.Error())
return
}
defer resp.Body.Close()
// 401是预期的(未登录)
if resp.StatusCode == 200 || resp.StatusCode == 401 {
atomic.AddInt64(&result.SuccessRequests, 1)
} else {
atomic.AddInt64(&result.FailedRequests, 1)
recordError(result, fmt.Sprintf("chat_messages_status_%d", resp.StatusCode))
}
}
// ============================================
// 辅助函数
// ============================================
func updateLatency(result *TestResult, latency int64) {
atomic.AddInt64(&result.TotalLatency, latency)
// 更新最小延迟
for {
current := atomic.LoadInt64(&result.MinLatency)
if latency >= current {
break
}
if atomic.CompareAndSwapInt64(&result.MinLatency, current, latency) {
break
}
}
// 更新最大延迟
for {
current := atomic.LoadInt64(&result.MaxLatency)
if latency <= current {
break
}
if atomic.CompareAndSwapInt64(&result.MaxLatency, current, latency) {
break
}
}
}
var errorMutex sync.Mutex
func recordError(result *TestResult, errMsg string) {
errorMutex.Lock()
defer errorMutex.Unlock()
result.Errors[errMsg]++
}
func printResult(result *TestResult) {
fmt.Printf("\n=== 压力测试结果 ===\n")
fmt.Printf("总请求数: %d\n", result.TotalRequests)
fmt.Printf("成功请求: %d (%.2f%%)\n",
result.SuccessRequests,
float64(result.SuccessRequests)/float64(result.TotalRequests)*100)
fmt.Printf("失败请求: %d (%.2f%%)\n",
result.FailedRequests,
float64(result.FailedRequests)/float64(result.TotalRequests)*100)
if result.TotalRequests > 0 {
avgLatency := result.TotalLatency / result.TotalRequests
fmt.Printf("\n延迟统计:\n")
fmt.Printf(" 最小延迟: %d ms\n", result.MinLatency)
fmt.Printf(" 平均延迟: %d ms\n", avgLatency)
fmt.Printf(" 最大延迟: %d ms\n", result.MaxLatency)
}
if len(result.Errors) > 0 {
fmt.Printf("\n错误统计:\n")
for err, count := range result.Errors {
fmt.Printf(" %s: %d 次\n", err, count)
}
}
qps := float64(result.TotalRequests) / float64(*duration)
fmt.Printf("\nQPS: %.2f\n", qps)
fmt.Printf("==================\n")
}