concurrency_cache_benchmark_test.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "testing"
  7. "time"
  8. "github.com/redis/go-redis/v9"
  9. )
  10. // 基准测试用 TTL 配置
  11. const benchSlotTTLMinutes = 15
  12. var benchSlotTTL = time.Duration(benchSlotTTLMinutes) * time.Minute
  13. // BenchmarkAccountConcurrency 用于对比 SCAN 与有序集合的计数性能。
  14. func BenchmarkAccountConcurrency(b *testing.B) {
  15. rdb := newBenchmarkRedisClient(b)
  16. defer func() {
  17. _ = rdb.Close()
  18. }()
  19. cache, _ := NewConcurrencyCache(rdb, benchSlotTTLMinutes, int(benchSlotTTL.Seconds())).(*concurrencyCache)
  20. ctx := context.Background()
  21. for _, size := range []int{10, 100, 1000} {
  22. size := size
  23. b.Run(fmt.Sprintf("zset/slots=%d", size), func(b *testing.B) {
  24. accountID := time.Now().UnixNano()
  25. key := accountSlotKey(accountID)
  26. b.StopTimer()
  27. members := make([]redis.Z, 0, size)
  28. now := float64(time.Now().Unix())
  29. for i := 0; i < size; i++ {
  30. members = append(members, redis.Z{
  31. Score: now,
  32. Member: fmt.Sprintf("req_%d", i),
  33. })
  34. }
  35. if err := rdb.ZAdd(ctx, key, members...).Err(); err != nil {
  36. b.Fatalf("初始化有序集合失败: %v", err)
  37. }
  38. if err := rdb.Expire(ctx, key, benchSlotTTL).Err(); err != nil {
  39. b.Fatalf("设置有序集合 TTL 失败: %v", err)
  40. }
  41. b.StartTimer()
  42. b.ReportAllocs()
  43. for i := 0; i < b.N; i++ {
  44. if _, err := cache.GetAccountConcurrency(ctx, accountID); err != nil {
  45. b.Fatalf("获取并发数量失败: %v", err)
  46. }
  47. }
  48. b.StopTimer()
  49. if err := rdb.Del(ctx, key).Err(); err != nil {
  50. b.Fatalf("清理有序集合失败: %v", err)
  51. }
  52. })
  53. b.Run(fmt.Sprintf("scan/slots=%d", size), func(b *testing.B) {
  54. accountID := time.Now().UnixNano()
  55. pattern := fmt.Sprintf("%s%d:*", accountSlotKeyPrefix, accountID)
  56. keys := make([]string, 0, size)
  57. b.StopTimer()
  58. pipe := rdb.Pipeline()
  59. for i := 0; i < size; i++ {
  60. key := fmt.Sprintf("%s%d:req_%d", accountSlotKeyPrefix, accountID, i)
  61. keys = append(keys, key)
  62. pipe.Set(ctx, key, "1", benchSlotTTL)
  63. }
  64. if _, err := pipe.Exec(ctx); err != nil {
  65. b.Fatalf("初始化扫描键失败: %v", err)
  66. }
  67. b.StartTimer()
  68. b.ReportAllocs()
  69. for i := 0; i < b.N; i++ {
  70. if _, err := scanSlotCount(ctx, rdb, pattern); err != nil {
  71. b.Fatalf("SCAN 计数失败: %v", err)
  72. }
  73. }
  74. b.StopTimer()
  75. if err := rdb.Del(ctx, keys...).Err(); err != nil {
  76. b.Fatalf("清理扫描键失败: %v", err)
  77. }
  78. })
  79. }
  80. }
  81. func scanSlotCount(ctx context.Context, rdb *redis.Client, pattern string) (int, error) {
  82. var cursor uint64
  83. count := 0
  84. for {
  85. keys, nextCursor, err := rdb.Scan(ctx, cursor, pattern, 100).Result()
  86. if err != nil {
  87. return 0, err
  88. }
  89. count += len(keys)
  90. if nextCursor == 0 {
  91. break
  92. }
  93. cursor = nextCursor
  94. }
  95. return count, nil
  96. }
  97. func newBenchmarkRedisClient(b *testing.B) *redis.Client {
  98. b.Helper()
  99. redisURL := os.Getenv("TEST_REDIS_URL")
  100. if redisURL == "" {
  101. b.Skip("未设置 TEST_REDIS_URL,跳过 Redis 基准测试")
  102. }
  103. opt, err := redis.ParseURL(redisURL)
  104. if err != nil {
  105. b.Fatalf("解析 TEST_REDIS_URL 失败: %v", err)
  106. }
  107. client := redis.NewClient(opt)
  108. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
  109. defer cancel()
  110. if err := client.Ping(ctx).Err(); err != nil {
  111. b.Fatalf("Redis 连接失败: %v", err)
  112. }
  113. return client
  114. }