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:
parent
355d7a18ae
commit
0979b60ff2
164
internal/benchmark/tools/loadgen.go
Normal file
164
internal/benchmark/tools/loadgen.go
Normal 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)
|
||||
}
|
||||
}
|
||||
147
internal/benchmark/tools/mock_backend.go
Normal file
147
internal/benchmark/tools/mock_backend.go
Normal 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,
|
||||
})
|
||||
}
|
||||
182
internal/benchmark/tools/testdata.go
Normal file
182
internal/benchmark/tools/testdata.go
Normal 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
252
internal/cache/cache_bench_test.go
vendored
Normal 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++
|
||||
}
|
||||
})
|
||||
}
|
||||
265
internal/loadbalance/balancer_bench_test.go
Normal file
265
internal/loadbalance/balancer_bench_test.go
Normal 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
430
internal/proxy/proxy_bench_test.go
Normal file
430
internal/proxy/proxy_bench_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user