feat(cache): 优化缓存系统并重构数据库连接管理
- 将Redis连接方式改为连接池模式,提升连接复用效率 - 修复缓存注释错误,统一标识数据库缓存逻辑 - 添加数据检索结果非空验证,避免空指针异常 - 在数据库操作中添加读写锁保护,确保并发安全性 - 实现数据库查询和执行操作的重试机制,增强稳定性 - 更新配置文件中的缓存和数据库设置,优化缓存策略 - 重构README文档,补充框架特性和性能测试数据 - 添加示例路由配置,完善快速入门指南
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user