test(cache,loadbalance,proxy,benchmark): 新增核心模块基准测试

- cache: LRU Get/Set 性能测试
- loadbalance: 负载均衡算法性能测试
- proxy: 代理处理性能测试
- benchmark/tools: 负载生成器和模拟后端工具

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-07 17:06:08 +08:00
parent 355d7a18ae
commit 0979b60ff2
6 changed files with 1440 additions and 0 deletions

View File

@ -0,0 +1,164 @@
// Package tools provides testing utilities and mock infrastructure for benchmark tests.
package tools
import (
"math"
"sort"
"sync"
"testing"
"time"
"github.com/valyala/fasthttp"
)
// FasthttpLoadGenerator is a load generator using fasthttp client.
type FasthttpLoadGenerator struct {
client *fasthttp.HostClient
addr string
stats LoadGenStats
mu sync.Mutex
}
// LoadGenStats contains load generator statistics.
type LoadGenStats struct {
TotalRequests int
SuccessCount int
ErrorCount int
TotalDuration time.Duration
MinLatency time.Duration
MaxLatency time.Duration
MeanLatency time.Duration
P50Latency time.Duration
P90Latency time.Duration
P99Latency time.Duration
QPS float64
Latencies []time.Duration // For percentile calculation
}
// NewFasthttpLoadGenerator creates a new load generator for the given address.
func NewFasthttpLoadGenerator(addr string) *FasthttpLoadGenerator {
return &FasthttpLoadGenerator{
client: &fasthttp.HostClient{
Addr: addr,
MaxConns: 1000,
},
addr: addr,
stats: LoadGenStats{
MinLatency: time.Duration(math.MaxInt64),
},
}
}
// Run executes a load test with n requests using the specified concurrency.
// Returns collected statistics.
func (lg *FasthttpLoadGenerator) Run(n int, concurrency int) *LoadGenStats {
var wg sync.WaitGroup
requestsPerWorker := n / concurrency
// Channels for collecting metrics
latencyChan := make(chan time.Duration, n)
errorChan := make(chan error, n)
start := time.Now()
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(resp)
for j := 0; j < requestsPerWorker; j++ {
req.SetRequestURI("http://" + lg.addr + "/")
req.Header.SetMethod("GET")
reqStart := time.Now()
err := lg.client.Do(req, resp)
latency := time.Since(reqStart)
latencyChan <- latency
if err != nil {
errorChan <- err
}
}
}()
}
wg.Wait()
close(latencyChan)
close(errorChan)
totalDuration := time.Since(start)
// Collect latencies
latencies := make([]time.Duration, 0, n)
for lat := range latencyChan {
latencies = append(latencies, lat)
}
// Count errors
errorCount := 0
for err := range errorChan {
_ = err // Error recorded, used for counting
errorCount++
}
// Calculate statistics
lg.mu.Lock()
defer lg.mu.Unlock()
lg.stats.TotalRequests = n
lg.stats.ErrorCount = errorCount
lg.stats.SuccessCount = n - errorCount
lg.stats.TotalDuration = totalDuration
lg.stats.QPS = float64(n) / totalDuration.Seconds()
lg.stats.Latencies = latencies
// Calculate latency distribution
if len(latencies) > 0 {
sort.Slice(latencies, func(i, j int) bool {
return latencies[i] < latencies[j]
})
lg.stats.MinLatency = latencies[0]
lg.stats.MaxLatency = latencies[len(latencies)-1]
// Calculate mean
var sum time.Duration
for _, l := range latencies {
sum += l
}
lg.stats.MeanLatency = sum / time.Duration(len(latencies))
// Calculate percentiles
lg.stats.P50Latency = latencies[len(latencies)*50/100]
lg.stats.P90Latency = latencies[len(latencies)*90/100]
lg.stats.P99Latency = latencies[len(latencies)*99/100]
}
return &lg.stats
}
// GetStats returns current statistics without running a test.
func (lg *FasthttpLoadGenerator) GetStats() *LoadGenStats {
lg.mu.Lock()
defer lg.mu.Unlock()
return &lg.stats
}
// RunParallel runs requests in parallel using testing.PB.
// This is designed for use with Go benchmark functions.
func (lg *FasthttpLoadGenerator) RunParallel(pb *testing.PB) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(resp)
for pb.Next() {
req.SetRequestURI("http://" + lg.addr + "/")
req.Header.SetMethod("GET")
lg.client.Do(req, resp)
}
}

View File

@ -0,0 +1,147 @@
// Package tools provides testing utilities and mock infrastructure for benchmark tests.
package tools
import (
"math/rand"
"sync"
"time"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil"
)
// BackendMode defines the response behavior of the mock backend.
type BackendMode int
const (
// ModeFixed returns a fixed response with configurable status and body.
ModeFixed BackendMode = iota
// ModeDelay adds artificial delay before responding.
ModeDelay
// ModeError returns errors for a percentage of requests.
ModeError
// ModeRandomResponse returns random status codes.
ModeRandomResponse
)
// MockBackendConfig configures the mock backend behavior.
type MockBackendConfig struct {
Mode BackendMode
StatusCode int
Body []byte
Delay time.Duration
ErrorRate float64 // 0.0 to 1.0, for ModeError
}
// MockBackend represents a mock fasthttp server for testing.
type MockBackend struct {
server *fasthttp.Server
config MockBackendConfig
mu sync.RWMutex
}
// StartMockFasthttpBackend starts a mock fasthttp backend server.
// Returns the server address and a cleanup function.
func StartMockFasthttpBackend(config MockBackendConfig) (string, func()) {
mb := &MockBackend{
config: config,
}
mb.server = &fasthttp.Server{
Handler: mb.handler,
}
// Use in-memory listener for testing
ln := fasthttputil.NewInmemoryListener()
// Start server in background
go func() {
mb.server.Serve(ln)
}()
addr := "127.0.0.1:0" // In-memory listener address
cleanup := func() {
mb.server.Shutdown()
ln.Close()
}
return addr, cleanup
}
// handler processes incoming requests based on the configured mode.
func (mb *MockBackend) handler(ctx *fasthttp.RequestCtx) {
mb.mu.RLock()
config := mb.config
mb.mu.RUnlock()
switch config.Mode {
case ModeDelay:
time.Sleep(config.Delay)
ctx.SetStatusCode(config.StatusCode)
ctx.Write(config.Body)
case ModeError:
if rand.Float64() < config.ErrorRate {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.WriteString("internal server error")
return
}
ctx.SetStatusCode(config.StatusCode)
ctx.Write(config.Body)
case ModeRandomResponse:
codes := []int{
fasthttp.StatusOK,
fasthttp.StatusCreated,
fasthttp.StatusNoContent,
fasthttp.StatusBadRequest,
fasthttp.StatusNotFound,
}
ctx.SetStatusCode(codes[rand.Intn(len(codes))])
ctx.Write(config.Body)
default: // ModeFixed
ctx.SetStatusCode(config.StatusCode)
ctx.Write(config.Body)
}
}
// SetConfig updates the backend configuration at runtime.
func (mb *MockBackend) SetConfig(config MockBackendConfig) {
mb.mu.Lock()
defer mb.mu.Unlock()
mb.config = config
}
// SimpleMockBackend creates a simple backend with fixed response.
// Returns address and cleanup function.
func SimpleMockBackend(statusCode int, body []byte) (string, func()) {
return StartMockFasthttpBackend(MockBackendConfig{
Mode: ModeFixed,
StatusCode: statusCode,
Body: body,
})
}
// DelayedMockBackend creates a backend with delayed responses.
// Returns address and cleanup function.
func DelayedMockBackend(delay time.Duration, body []byte) (string, func()) {
return StartMockFasthttpBackend(MockBackendConfig{
Mode: ModeDelay,
StatusCode: fasthttp.StatusOK,
Body: body,
Delay: delay,
})
}
// ErrorMockBackend creates a backend that returns errors at the specified rate.
// Returns address and cleanup function.
func ErrorMockBackend(errorRate float64, body []byte) (string, func()) {
return StartMockFasthttpBackend(MockBackendConfig{
Mode: ModeError,
StatusCode: fasthttp.StatusOK,
Body: body,
ErrorRate: errorRate,
})
}

View File

@ -0,0 +1,182 @@
// Package tools provides testing utilities and mock infrastructure for benchmark tests.
package tools
import (
"fmt"
"math/rand"
"time"
)
// TestDataSize represents predefined test data sizes.
type TestDataSize int
const (
// Size1KB represents 1KB test data.
Size1KB TestDataSize = 1024
// Size10KB represents 10KB test data.
Size10KB TestDataSize = 10 * 1024
// Size100KB represents 100KB test data.
Size100KB TestDataSize = 100 * 1024
// Size1MB represents 1MB test data.
Size1MB TestDataSize = 1024 * 1024
// Size10MB represents 10MB test data.
Size10MB TestDataSize = 10 * 1024 * 1024
)
// GenerateTestData generates test data of the specified size.
func GenerateTestData(size TestDataSize) []byte {
data := make([]byte, size)
// Fill with random data for compression testing
for i := range data {
data[i] = byte(rand.Intn(256))
}
return data
}
// GenerateTestDataString generates test data as string.
func GenerateTestDataString(size TestDataSize) string {
return string(GenerateTestData(size))
}
// TestTarget represents a backend target for testing.
type TestTarget struct {
Address string
Weight int
}
// createTestTargets creates n test backend targets with mock servers.
// Returns the target configurations and a cleanup function.
func createTestTargets(n int) ([]TestTarget, func()) {
targets := make([]TestTarget, n)
cleanups := make([]func(), n)
for i := 0; i < n; i++ {
body := GenerateTestData(Size1KB)
addr, cleanup := SimpleMockBackend(200, body)
targets[i] = TestTarget{
Address: addr,
Weight: 1,
}
cleanups[i] = cleanup
}
cleanupAll := func() {
for _, cleanup := range cleanups {
if cleanup != nil {
cleanup()
}
}
}
return targets, cleanupAll
}
// CreateTestTargets creates n test backend targets.
// Returns the target configurations and a cleanup function.
func CreateTestTargets(n int) ([]TestTarget, func()) {
return createTestTargets(n)
}
// CreateWeightedTestTargets creates n test backend targets with varying weights.
// Returns the target configurations and a cleanup function.
func CreateWeightedTestTargets(n int) ([]TestTarget, func()) {
targets := make([]TestTarget, n)
cleanups := make([]func(), n)
for i := 0; i < n; i++ {
body := GenerateTestData(Size1KB)
addr, cleanup := SimpleMockBackend(200, body)
// Vary weights: 1, 2, 3, etc.
targets[i] = TestTarget{
Address: addr,
Weight: i + 1,
}
cleanups[i] = cleanup
}
cleanupAll := func() {
for _, cleanup := range cleanups {
if cleanup != nil {
cleanup()
}
}
}
return targets, cleanupAll
}
// CreateDelayedTestTargets creates n test backend targets with varying delays.
// Useful for testing timeout and latency-related behavior.
func CreateDelayedTestTargets(n int, baseDelay time.Duration) ([]TestTarget, func()) {
targets := make([]TestTarget, n)
cleanups := make([]func(), n)
for i := 0; i < n; i++ {
body := GenerateTestData(Size1KB)
// Each target has increasing delay
delay := baseDelay * time.Duration(i+1)
addr, cleanup := DelayedMockBackend(delay, body)
targets[i] = TestTarget{
Address: addr,
Weight: 1,
}
cleanups[i] = cleanup
}
cleanupAll := func() {
for _, cleanup := range cleanups {
if cleanup != nil {
cleanup()
}
}
}
return targets, cleanupAll
}
// CreateErrorTestTargets creates n test backend targets with varying error rates.
// Useful for testing error handling and circuit breaker behavior.
func CreateErrorTestTargets(n int, baseErrorRate float64) ([]TestTarget, func()) {
targets := make([]TestTarget, n)
cleanups := make([]func(), n)
for i := 0; i < n; i++ {
body := GenerateTestData(Size1KB)
// Vary error rates slightly per target
errorRate := baseErrorRate + float64(i)*0.05
if errorRate > 1.0 {
errorRate = 1.0
}
addr, cleanup := ErrorMockBackend(errorRate, body)
targets[i] = TestTarget{
Address: addr,
Weight: 1,
}
cleanups[i] = cleanup
}
cleanupAll := func() {
for _, cleanup := range cleanups {
if cleanup != nil {
cleanup()
}
}
}
return targets, cleanupAll
}
// GenerateCacheKey generates a unique cache key for testing.
func GenerateCacheKey(prefix string, index int) string {
return fmt.Sprintf("%s-key-%d", prefix, index)
}
// GenerateCacheValue generates a cache value of specified size.
func GenerateCacheValue(size TestDataSize) []byte {
return GenerateTestData(size)
}
// GenerateRandomCacheKey generates a random cache key.
func GenerateRandomCacheKey() string {
return fmt.Sprintf("random-key-%d-%d", time.Now().UnixNano(), rand.Int())
}

252
internal/cache/cache_bench_test.go vendored Normal file
View File

@ -0,0 +1,252 @@
// Package cache 提供缓存模块的基准测试。
//
// 该文件测试缓存模块的性能,包括:
// - LRU Get/Set 操作性能
// - 并发读写性能
// - 不同缓存大小下的性能表现
//
// 作者xfy
package cache
import (
"fmt"
"testing"
"time"
)
// BenchmarkFileCacheGet 测试热点读取场景下的 Get 性能。
// 模拟缓存命中率高的场景,测试 LRU 链表的访问效率。
func BenchmarkFileCacheGet(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
fc := NewFileCache(int64(size), 0, 1*time.Hour)
// 预填充缓存
for i := 0; i < size; i++ {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
// 热点读取:集中在前面 10% 的数据
path := fmt.Sprintf("/file%d.txt", i%(size/10+1))
fc.Get(path)
i++
}
})
})
}
}
// BenchmarkFileCacheSet 测试 Set 操作性能,包括 LRU 淘汰开销。
// 测试在缓存达到容量限制后的写入性能。
func BenchmarkFileCacheSet(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
fc := NewFileCache(int64(size), 0, 1*time.Hour)
// 预填充到容量上限
for i := 0; i < size; i++ {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
path := fmt.Sprintf("/newfile%d.txt", i)
data := []byte("new cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
})
}
}
// BenchmarkFileCacheSetNoEviction 测试无淘汰场景下的 Set 性能。
// 此时缓存未满,没有 LRU 淘汰开销。
func BenchmarkFileCacheSetNoEviction(b *testing.B) {
fc := NewFileCache(int64(b.N+1000), 0, 1*time.Hour)
b.ResetTimer()
for i := 0; i < b.N; i++ {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
}
// BenchmarkFileCacheConcurrent 测试并发读写混合负载性能。
// 使用 90% Get / 10% Set 的混合负载模拟真实场景。
func BenchmarkFileCacheConcurrent(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("Size%d", size), func(b *testing.B) {
fc := NewFileCache(int64(size), 0, 1*time.Hour)
// 预填充缓存
for i := 0; i < size; i++ {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
// 90% Get, 10% Set
if i%10 == 0 {
path := fmt.Sprintf("/newfile%d.txt", i)
data := []byte("updated data content")
_ = fc.Set(path, data, int64(len(data)), time.Now())
} else {
path := fmt.Sprintf("/file%d.txt", i%size)
fc.Get(path)
}
i++
}
})
})
}
}
// BenchmarkFileCacheGetOnly 测试纯读场景下的性能。
// 模拟静态文件服务的缓存读取。
func BenchmarkFileCacheGetOnly(b *testing.B) {
fc := NewFileCache(1000, 0, 1*time.Hour)
// 预填充缓存
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("/static/file%d.css", i)
data := make([]byte, 1024) // 1KB 数据
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
path := fmt.Sprintf("/static/file%d.css", i%1000)
fc.Get(path)
i++
}
})
}
// BenchmarkFileCacheSizeEviction 测试基于内存大小的淘汰性能。
// 测试当缓存超过内存限制时的淘汰开销。
func BenchmarkFileCacheSizeEviction(b *testing.B) {
// 限制最大 1MB 内存
maxSize := int64(1024 * 1024)
fc := NewFileCache(0, maxSize, 1*time.Hour)
// 预填充到接近容量上限
data := make([]byte, 1024) // 1KB 每条
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("/file%d.txt", i)
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
path := fmt.Sprintf("/newfile%d.txt", i)
newData := make([]byte, 1024)
_ = fc.Set(path, newData, int64(len(newData)), time.Now())
}
}
// BenchmarkFileCacheLRUTouch 测试 LRU 链表更新开销。
// 频繁访问同一批条目,观察 LRU 移动性能。
func BenchmarkFileCacheLRUTouch(b *testing.B) {
fc := NewFileCache(100, 0, 1*time.Hour)
// 预填充缓存
for i := 0; i < 100; i++ {
path := fmt.Sprintf("/file%d.txt", i)
data := []byte("cached data")
_ = fc.Set(path, data, int64(len(data)), time.Now())
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 按顺序访问,触发 LRU 链表更新
path := fmt.Sprintf("/file%d.txt", i%100)
fc.Get(path)
}
}
// BenchmarkProxyCacheGet 测试代理缓存 Get 性能。
func BenchmarkProxyCacheGet(b *testing.B) {
pc := NewProxyCache(nil, false, 0)
// 预填充缓存
for i := 0; i < 1000; i++ {
key := fmt.Sprintf("key%d", i)
data := []byte("response body")
headers := map[string]string{"Content-Type": "application/json"}
pc.Set(key, data, headers, 200, 10*time.Minute)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
key := fmt.Sprintf("key%d", i%1000)
pc.Get(key)
i++
}
})
}
// BenchmarkProxyCacheSet 测试代理缓存 Set 性能。
func BenchmarkProxyCacheSet(b *testing.B) {
pc := NewProxyCache(nil, false, 0)
data := []byte("response body")
headers := map[string]string{"Content-Type": "application/json"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := fmt.Sprintf("key%d", i)
pc.Set(key, data, headers, 200, 10*time.Minute)
}
}
// BenchmarkProxyCacheConcurrent 测试代理缓存并发混合负载。
// 使用 90% Get / 10% Set 的混合负载。
func BenchmarkProxyCacheConcurrent(b *testing.B) {
pc := NewProxyCache(nil, false, 0)
// 预填充缓存
for i := 0; i < 1000; i++ {
key := fmt.Sprintf("key%d", i)
data := []byte("response body")
headers := map[string]string{"Content-Type": "application/json"}
pc.Set(key, data, headers, 200, 10*time.Minute)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
if i%10 == 0 {
key := fmt.Sprintf("newkey%d", i)
data := []byte("new response body")
headers := map[string]string{"Content-Type": "application/json"}
pc.Set(key, data, headers, 200, 10*time.Minute)
} else {
key := fmt.Sprintf("key%d", i%1000)
pc.Get(key)
}
i++
}
})
}

View File

@ -0,0 +1,265 @@
// Package loadbalance 提供负载均衡算法的基准测试。
package loadbalance
import (
"fmt"
"sync/atomic"
"testing"
)
// generateTargets 生成指定数量的健康目标用于基准测试。
func generateTargets(count int) []*Target {
targets := make([]*Target, count)
for i := 0; i < count; i++ {
targets[i] = &Target{
URL: fmt.Sprintf("http://backend%d:8080", i),
Weight: 1,
}
targets[i].Healthy.Store(true)
}
return targets
}
// generateWeightedTargets 生成带权重的目标用于基准测试。
func generateWeightedTargets(count int, weights []int) []*Target {
targets := make([]*Target, count)
for i := 0; i < count; i++ {
weight := 1
if i < len(weights) {
weight = weights[i]
}
targets[i] = &Target{
URL: fmt.Sprintf("http://backend%d:8080", i),
Weight: weight,
}
targets[i].Healthy.Store(true)
}
return targets
}
// BenchmarkRoundRobinSelect 基准测试轮询算法。
func BenchmarkRoundRobinSelect(b *testing.B) {
testCases := []struct {
name string
targets int
}{
{"3targets", 3},
{"50targets", 50},
{"200targets", 200},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := generateTargets(tc.targets)
rr := NewRoundRobin()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
rr.Select(targets)
}
})
})
}
}
// BenchmarkWeightedRoundRobin 基准测试加权轮询算法。
func BenchmarkWeightedRoundRobin(b *testing.B) {
testCases := []struct {
name string
targets int
weights []int
}{
{"3targets_equal", 3, []int{1, 1, 1}},
{"3targets_weighted", 3, []int{1, 5, 10}},
{"50targets_equal", 50, nil},
{"50targets_weighted", 50, []int{1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5}},
{"200targets_equal", 200, nil},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := generateWeightedTargets(tc.targets, tc.weights)
wrr := NewWeightedRoundRobin()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
wrr.Select(targets)
}
})
})
}
}
// BenchmarkConsistentHashSelect 基准测试一致性哈希算法。
func BenchmarkConsistentHashSelect(b *testing.B) {
testCases := []struct {
name string
targets int
virtualNodes int
}{
{"10targets_50vnodes", 10, 50},
{"10targets_150vnodes", 10, 150},
{"10targets_200vnodes", 10, 200},
{"50targets_150vnodes", 50, 150},
{"100targets_150vnodes", 100, 150},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := generateTargets(tc.targets)
ch := NewConsistentHash(tc.virtualNodes, "ip")
ch.Rebuild(targets)
keys := make([]string, 100)
for i := 0; i < 100; i++ {
keys[i] = fmt.Sprintf("192.168.1.%d", i)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := uint64(0)
for pb.Next() {
keyIdx := atomic.AddUint64(&i, 1) % uint64(len(keys))
ch.SelectByKey(targets, keys[keyIdx])
}
})
})
}
}
// BenchmarkConsistentHashRebuild 基准测试一致性哈希环重建性能。
func BenchmarkConsistentHashRebuild(b *testing.B) {
testCases := []struct {
name string
targets int
virtualNodes int
}{
{"10targets_150vnodes", 10, 150},
{"50targets_150vnodes", 50, 150},
{"100targets_150vnodes", 100, 150},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := generateTargets(tc.targets)
ch := NewConsistentHash(tc.virtualNodes, "ip")
b.ResetTimer()
for i := 0; i < b.N; i++ {
ch.Rebuild(targets)
}
})
}
}
// BenchmarkLeastConnSelect 基准测试最少连接算法。
func BenchmarkLeastConnSelect(b *testing.B) {
testCases := []struct {
name string
targets int
}{
{"3targets", 3},
{"50targets", 50},
{"200targets", 200},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := generateTargets(tc.targets)
// 设置不同的连接数以模拟真实场景
for i, t := range targets {
t.Connections = int64(i * 10)
}
lc := NewLeastConnections()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
lc.Select(targets)
}
})
})
}
}
// BenchmarkIPHashSelect 基准测试 IP 哈希算法。
func BenchmarkIPHashSelect(b *testing.B) {
testCases := []struct {
name string
targets int
}{
{"3targets", 3},
{"50targets", 50},
{"200targets", 200},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := generateTargets(tc.targets)
iph := NewIPHash()
ips := make([]string, 100)
for i := 0; i < 100; i++ {
ips[i] = fmt.Sprintf("192.168.1.%d", i)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := uint64(0)
for pb.Next() {
ipIdx := atomic.AddUint64(&i, 1) % uint64(len(ips))
iph.SelectByIP(targets, ips[ipIdx])
}
})
})
}
}
// BenchmarkAllBalancers 对比所有负载均衡算法的性能。
func BenchmarkAllBalancers(b *testing.B) {
targets := generateTargets(50)
weightedTargets := generateWeightedTargets(50, nil)
b.Run("RoundRobin", func(b *testing.B) {
rr := NewRoundRobin()
b.ResetTimer()
for i := 0; i < b.N; i++ {
rr.Select(targets)
}
})
b.Run("WeightedRoundRobin", func(b *testing.B) {
wrr := NewWeightedRoundRobin()
b.ResetTimer()
for i := 0; i < b.N; i++ {
wrr.Select(weightedTargets)
}
})
b.Run("LeastConnections", func(b *testing.B) {
lc := NewLeastConnections()
b.ResetTimer()
for i := 0; i < b.N; i++ {
lc.Select(targets)
}
})
b.Run("IPHash", func(b *testing.B) {
iph := NewIPHash()
b.ResetTimer()
for i := 0; i < b.N; i++ {
iph.SelectByIP(targets, "192.168.1.100")
}
})
b.Run("ConsistentHash", func(b *testing.B) {
ch := NewConsistentHash(150, "ip")
ch.Rebuild(targets)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ch.SelectByKey(targets, "192.168.1.100")
}
})
}

View File

@ -0,0 +1,430 @@
// Package proxy 提供反向代理性能的基准测试。
package proxy
import (
"fmt"
"sync/atomic"
"testing"
"time"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil"
"rua.plus/lolly/internal/benchmark/tools"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/loadbalance"
)
// setupMockBackend 设置一个模拟后端服务器用于基准测试。
// 返回监听器地址和清理函数。
func setupMockBackend(body []byte) (string, func()) {
ln := fasthttputil.NewInmemoryListener()
server := &fasthttp.Server{
Handler: func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK)
ctx.Write(body)
},
}
go func() {
server.Serve(ln)
}()
// 等待服务器启动
time.Sleep(5 * time.Millisecond)
addr := ln.Addr().String()
cleanup := func() {
ln.Close()
}
return addr, cleanup
}
// BenchmarkProxyForward 基准测试代理转发性能。
func BenchmarkProxyForward(b *testing.B) {
testCases := []struct {
name string
concurrency int
}{
{"concurrency1", 1},
{"concurrency10", 10},
{"concurrency100", 100},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
addr, cleanup := setupMockBackend([]byte("Hello, World!"))
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
p.ServeHTTP(ctx)
}
})
})
}
}
// BenchmarkProxyForwardSmallRequest 基准测试小请求/小响应代理转发。
func BenchmarkProxyForwardSmallRequest(b *testing.B) {
smallBody := make([]byte, 100)
for i := range smallBody {
smallBody[i] = byte('a' + i%26)
}
addr, cleanup := setupMockBackend(smallBody)
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodPost)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.SetBodyString(string(smallBody))
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyForwardLargeRequest 基准测试大请求/大响应代理转发。
func BenchmarkProxyForwardLargeRequest(b *testing.B) {
// 1KB 请求10KB 响应
requestBody := make([]byte, 1024)
for i := range requestBody {
requestBody[i] = byte('a' + i%26)
}
responseBody := make([]byte, 10*1024)
for i := range responseBody {
responseBody[i] = byte('A' + i%26)
}
addr, cleanup := setupMockBackend(responseBody)
defer cleanup()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodPost)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.SetBody(requestBody)
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyForwardMultipleTargets 基准测试多目标代理转发。
func BenchmarkProxyForwardMultipleTargets(b *testing.B) {
smallBody := []byte("OK")
numTargets := 5
targets := make([]*loadbalance.Target, numTargets)
cleanups := make([]func(), numTargets)
for i := 0; i < numTargets; i++ {
addr, cleanup := setupMockBackend(smallBody)
cleanups[i] = cleanup
targets[i] = &loadbalance.Target{
URL: "http://" + addr,
Weight: i + 1,
}
targets[i].Healthy.Store(true)
}
defer func() {
for _, cleanup := range cleanups {
cleanup()
}
}()
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "weighted_round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := NewProxy(cfg, targets, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyHostClient 基准测试 HostClient 性能。
func BenchmarkProxyHostClient(b *testing.B) {
smallBody := []byte("Hello")
addr, cleanup := setupMockBackend(smallBody)
defer cleanup()
timeout := config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
}
client := createHostClient("http://"+addr, timeout, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
req.SetRequestURI("http://" + addr + "/api/test")
req.Header.SetMethod(fasthttp.MethodGet)
client.Do(req, resp)
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}
}
// BenchmarkProxyHostClientParallel 基准测试 HostClient 并行性能。
func BenchmarkProxyHostClientParallel(b *testing.B) {
smallBody := []byte("Hello")
addr, cleanup := setupMockBackend(smallBody)
defer cleanup()
timeout := config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
}
client := createHostClient("http://"+addr, timeout, nil)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
req.SetRequestURI("http://" + addr + "/api/test")
req.Header.SetMethod(fasthttp.MethodGet)
client.Do(req, resp)
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}
})
}
// BenchmarkProxyWithMockBackend 基准测试使用 mock_backend 工具的代理转发。
func BenchmarkProxyWithMockBackend(b *testing.B) {
// 使用 tools 包启动 mock 后端
addr, cleanup := tools.SimpleMockBackend(fasthttp.StatusOK, []byte("Hello from mock backend"))
defer cleanup()
// 等待服务器完全启动
time.Sleep(10 * time.Millisecond)
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
targets := []*loadbalance.Target{
{URL: "http://" + addr},
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
p.ServeHTTP(ctx)
}
})
}
// BenchmarkProxyLoadBalancerSelection 基准测试代理负载均衡器选择性能。
func BenchmarkProxyLoadBalancerSelection(b *testing.B) {
testCases := []struct {
name string
loadBalance string
targetCount int
}{
{"round_robin_3", "round_robin", 3},
{"round_robin_50", "round_robin", 50},
{"weighted_round_robin_3", "weighted_round_robin", 3},
{"least_conn_3", "least_conn", 3},
{"ip_hash_3", "ip_hash", 3},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
targets := make([]*loadbalance.Target, tc.targetCount)
for i := 0; i < tc.targetCount; i++ {
targets[i] = &loadbalance.Target{
URL: fmt.Sprintf("http://backend%d:8080", i),
Weight: i + 1,
}
targets[i].Healthy.Store(true)
}
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: tc.loadBalance,
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
}
p, err := NewProxy(cfg, targets, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
counter := uint64(0)
// 每个 goroutine 使用独立的上下文
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/api/test")
for pb.Next() {
idx := atomic.AddUint64(&counter, 1)
ctx.Request.Header.Set("X-Forwarded-For", fmt.Sprintf("192.168.1.%d", idx%255))
p.selectTarget(ctx)
}
})
})
}
}
// BenchmarkProxyHeaderProcessing 基准测试代理请求头处理性能。
func BenchmarkProxyHeaderProcessing(b *testing.B) {
target := &loadbalance.Target{URL: "http://localhost:8080"}
cfg := &config.ProxyConfig{
Path: "/api",
LoadBalance: "round_robin",
Timeout: config.ProxyTimeout{
Connect: 5 * time.Second,
Read: 30 * time.Second,
Write: 30 * time.Second,
},
Headers: config.ProxyHeaders{
SetRequest: map[string]string{
"X-Custom-Header": "custom-value",
"X-Another": "another-value",
},
Remove: []string{"X-Remove-Me"},
},
}
targets := []*loadbalance.Target{
target,
}
targets[0].Healthy.Store(true)
p, err := NewProxy(cfg, targets, nil)
if err != nil {
b.Fatalf("NewProxy() error: %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
ctx.Request.SetRequestURI("/api/test")
ctx.Request.Header.Set("X-Forwarded-For", "192.168.1.100")
ctx.Request.Header.Set("X-Remove-Me", "should-be-removed")
p.modifyRequestHeaders(ctx, target)
}
})
}