- 将Redis连接方式改为连接池模式,提升连接复用效率 - 修复缓存注释错误,统一标识数据库缓存逻辑 - 添加数据检索结果非空验证,避免空指针异常 - 在数据库操作中添加读写锁保护,确保并发安全性 - 实现数据库查询和执行操作的重试机制,增强稳定性 - 更新配置文件中的缓存和数据库设置,优化缓存策略 - 重构README文档,补充框架特性和性能测试数据 - 添加示例路由配置,完善快速入门指南
328 lines
7.9 KiB
Go
328 lines
7.9 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"runtime"
|
||
"sync"
|
||
"sync/atomic"
|
||
"time"
|
||
)
|
||
|
||
func main() {
|
||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||
|
||
fmt.Println("==========================================")
|
||
fmt.Println(" 🔥 HoTime 极限压力测试 🔥")
|
||
fmt.Println("==========================================")
|
||
fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU())
|
||
fmt.Println()
|
||
|
||
baseURL := "http://127.0.0.1:8081/app/test/hello"
|
||
|
||
// 检查服务
|
||
fmt.Println("正在检查服务是否可用...")
|
||
client := &http.Client{Timeout: 3 * time.Second}
|
||
resp, err := client.Get(baseURL)
|
||
if err != nil || resp.StatusCode != 200 {
|
||
fmt.Println("❌ 服务不可用,请先启动示例应用")
|
||
return
|
||
}
|
||
resp.Body.Close()
|
||
fmt.Println("✅ 服务已就绪")
|
||
fmt.Println()
|
||
|
||
// 预热
|
||
fmt.Println("🔄 预热中 (5秒)...")
|
||
doWarmup(baseURL, 100, 5*time.Second)
|
||
fmt.Println("✅ 预热完成")
|
||
fmt.Println()
|
||
|
||
// 极限测试配置
|
||
concurrencyLevels := []int{500, 1000, 2000, 5000, 10000}
|
||
testDuration := 15 * time.Second
|
||
|
||
var maxQPS float64
|
||
var maxConcurrency int
|
||
|
||
for i, concurrency := range concurrencyLevels {
|
||
fmt.Printf("══════════════════════════════════════════\n")
|
||
fmt.Printf("【极限测试 %d】并发数: %d, 持续时间: %v\n", i+1, concurrency, testDuration)
|
||
fmt.Printf("══════════════════════════════════════════\n")
|
||
|
||
total, success, failed, qps, minLat, avgLat, maxLat, p50, p90, p99 := doBenchmark(baseURL, concurrency, testDuration)
|
||
|
||
successRate := float64(success) / float64(total) * 100
|
||
|
||
fmt.Printf("总请求数: %d\n", total)
|
||
fmt.Printf("成功请求: %d\n", success)
|
||
fmt.Printf("失败请求: %d\n", failed)
|
||
fmt.Printf("成功率: %.2f%%\n", successRate)
|
||
fmt.Printf("QPS: %.2f 请求/秒\n", qps)
|
||
fmt.Println("------------------------------------------")
|
||
fmt.Printf("最小延迟: %v\n", time.Duration(minLat))
|
||
fmt.Printf("平均延迟: %v\n", time.Duration(avgLat))
|
||
fmt.Printf("P50延迟: %v\n", time.Duration(p50))
|
||
fmt.Printf("P90延迟: %v\n", time.Duration(p90))
|
||
fmt.Printf("P99延迟: %v\n", time.Duration(p99))
|
||
fmt.Printf("最大延迟: %v\n", time.Duration(maxLat))
|
||
|
||
// 性能评级
|
||
fmt.Print("\n性能评级: ")
|
||
switch {
|
||
case qps >= 200000:
|
||
fmt.Println("🏆 卓越 (QPS >= 200K)")
|
||
case qps >= 100000:
|
||
fmt.Println("🚀 优秀 (QPS >= 100K)")
|
||
case qps >= 50000:
|
||
fmt.Println("⭐ 良好 (QPS >= 50K)")
|
||
case qps >= 20000:
|
||
fmt.Println("👍 中上 (QPS >= 20K)")
|
||
case qps >= 10000:
|
||
fmt.Println("📊 中等 (QPS >= 10K)")
|
||
default:
|
||
fmt.Println("⚠️ 一般 (QPS < 10K)")
|
||
}
|
||
|
||
if qps > maxQPS {
|
||
maxQPS = qps
|
||
maxConcurrency = concurrency
|
||
}
|
||
|
||
// 检查是否达到瓶颈
|
||
if successRate < 95 {
|
||
fmt.Println("\n⚠️ 成功率低于95%,已达到服务极限!")
|
||
break
|
||
}
|
||
if avgLat > int64(100*time.Millisecond) {
|
||
fmt.Println("\n⚠️ 平均延迟超过100ms,已达到服务极限!")
|
||
break
|
||
}
|
||
|
||
fmt.Println()
|
||
if i < len(concurrencyLevels)-1 {
|
||
fmt.Println("冷却 5 秒...")
|
||
time.Sleep(5 * time.Second)
|
||
fmt.Println()
|
||
}
|
||
}
|
||
|
||
// 最终报告
|
||
fmt.Println()
|
||
fmt.Println("══════════════════════════════════════════")
|
||
fmt.Println(" 📊 极限测试总结")
|
||
fmt.Println("══════════════════════════════════════════")
|
||
fmt.Printf("最高 QPS: %.2f 请求/秒\n", maxQPS)
|
||
fmt.Printf("最佳并发数: %d\n", maxConcurrency)
|
||
fmt.Println()
|
||
|
||
// 并发用户估算
|
||
fmt.Println("📈 并发用户数估算:")
|
||
fmt.Println("------------------------------------------")
|
||
fmt.Printf("高频交互(1秒/次): ~%d 用户\n", int(maxQPS*1))
|
||
fmt.Printf("活跃用户(5秒/次): ~%d 用户\n", int(maxQPS*5))
|
||
fmt.Printf("普通浏览(10秒/次): ~%d 用户\n", int(maxQPS*10))
|
||
fmt.Printf("低频访问(30秒/次): ~%d 用户\n", int(maxQPS*30))
|
||
fmt.Println()
|
||
fmt.Println("💡 生产环境建议保留 30-50% 性能余量")
|
||
fmt.Printf(" 安全并发用户数: %d - %d (普通浏览场景)\n",
|
||
int(maxQPS*10*0.5), int(maxQPS*10*0.7))
|
||
}
|
||
|
||
func doWarmup(url string, concurrency int, duration time.Duration) {
|
||
client := &http.Client{
|
||
Timeout: 5 * time.Second,
|
||
Transport: &http.Transport{
|
||
MaxIdleConns: concurrency * 2,
|
||
MaxIdleConnsPerHost: concurrency * 2,
|
||
},
|
||
}
|
||
|
||
done := make(chan struct{})
|
||
var wg sync.WaitGroup
|
||
|
||
for i := 0; i < concurrency; i++ {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
for {
|
||
select {
|
||
case <-done:
|
||
return
|
||
default:
|
||
resp, err := client.Get(url)
|
||
if err == nil {
|
||
io.Copy(io.Discard, resp.Body)
|
||
resp.Body.Close()
|
||
}
|
||
}
|
||
}
|
||
}()
|
||
}
|
||
|
||
time.Sleep(duration)
|
||
close(done)
|
||
wg.Wait()
|
||
}
|
||
|
||
func doBenchmark(url string, concurrency int, duration time.Duration) (total, success, failed int64, qps float64, minLat, avgLat, maxLat, p50, p90, p99 int64) {
|
||
var totalLatency int64
|
||
minLat = int64(time.Hour)
|
||
var mu sync.Mutex
|
||
|
||
// 采样收集器
|
||
latencies := make([]int64, 0, 50000)
|
||
var latMu sync.Mutex
|
||
|
||
client := &http.Client{
|
||
Timeout: 10 * time.Second,
|
||
Transport: &http.Transport{
|
||
MaxIdleConns: concurrency * 2,
|
||
MaxIdleConnsPerHost: concurrency * 2,
|
||
MaxConnsPerHost: concurrency * 2,
|
||
IdleConnTimeout: 90 * time.Second,
|
||
DisableCompression: true,
|
||
},
|
||
}
|
||
|
||
done := make(chan struct{})
|
||
var wg sync.WaitGroup
|
||
startTime := time.Now()
|
||
|
||
for i := 0; i < concurrency; i++ {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
for {
|
||
select {
|
||
case <-done:
|
||
return
|
||
default:
|
||
reqStart := time.Now()
|
||
ok := doRequest(client, url)
|
||
lat := time.Since(reqStart).Nanoseconds()
|
||
|
||
atomic.AddInt64(&total, 1)
|
||
atomic.AddInt64(&totalLatency, lat)
|
||
|
||
if ok {
|
||
atomic.AddInt64(&success, 1)
|
||
} else {
|
||
atomic.AddInt64(&failed, 1)
|
||
}
|
||
|
||
// 采样 (每50个采1个)
|
||
if atomic.LoadInt64(&total)%50 == 0 {
|
||
latMu.Lock()
|
||
latencies = append(latencies, lat)
|
||
latMu.Unlock()
|
||
}
|
||
|
||
mu.Lock()
|
||
if lat < minLat {
|
||
minLat = lat
|
||
}
|
||
if lat > maxLat {
|
||
maxLat = lat
|
||
}
|
||
mu.Unlock()
|
||
}
|
||
}
|
||
}()
|
||
}
|
||
|
||
time.Sleep(duration)
|
||
close(done)
|
||
wg.Wait()
|
||
|
||
totalDuration := time.Since(startTime)
|
||
|
||
if total > 0 {
|
||
avgLat = totalLatency / total
|
||
qps = float64(total) / totalDuration.Seconds()
|
||
}
|
||
|
||
// 计算百分位
|
||
if len(latencies) > 0 {
|
||
p50 = getPercentile(latencies, 0.50)
|
||
p90 = getPercentile(latencies, 0.90)
|
||
p99 = getPercentile(latencies, 0.99)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func doRequest(client *http.Client, url string) bool {
|
||
resp, err := client.Get(url)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
if resp.StatusCode != 200 {
|
||
return false
|
||
}
|
||
|
||
var result map[string]interface{}
|
||
if err := json.Unmarshal(body, &result); err != nil {
|
||
return false
|
||
}
|
||
|
||
if status, ok := result["status"].(float64); !ok || status != 0 {
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
func getPercentile(arr []int64, p float64) int64 {
|
||
n := len(arr)
|
||
if n == 0 {
|
||
return 0
|
||
}
|
||
|
||
// 复制一份避免修改原数组
|
||
tmp := make([]int64, n)
|
||
copy(tmp, arr)
|
||
|
||
idx := int(float64(n) * p)
|
||
if idx >= n {
|
||
idx = n - 1
|
||
}
|
||
|
||
return quickSelect(tmp, idx)
|
||
}
|
||
|
||
func quickSelect(arr []int64, k int) int64 {
|
||
if len(arr) == 1 {
|
||
return arr[0]
|
||
}
|
||
|
||
pivot := arr[len(arr)/2]
|
||
var left, right, equal []int64
|
||
|
||
for _, v := range arr {
|
||
if v < pivot {
|
||
left = append(left, v)
|
||
} else if v > pivot {
|
||
right = append(right, v)
|
||
} else {
|
||
equal = append(equal, v)
|
||
}
|
||
}
|
||
|
||
if k < len(left) {
|
||
return quickSelect(left, k)
|
||
} else if k < len(left)+len(equal) {
|
||
return pivot
|
||
}
|
||
return quickSelect(right, k-len(left)-len(equal))
|
||
}
|