batch_image_download_limiter.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package repository
  2. import (
  3. "context"
  4. "sync"
  5. "time"
  6. "github.com/Wei-Shaw/sub2api/internal/config"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. "github.com/redis/go-redis/v9"
  9. )
  10. const (
  11. defaultBatchImageDownloadActivePrefix = "batch_image:download:active:"
  12. defaultBatchImageDownloadActiveTTL = 10 * time.Minute
  13. defaultBatchImageDownloadConcurrency = 2
  14. )
  15. var batchImageDownloadAcquireScript = redis.NewScript(`
  16. local current = tonumber(redis.call("GET", KEYS[1]) or "0")
  17. local max = tonumber(ARGV[1])
  18. if current >= max then
  19. return 0
  20. end
  21. redis.call("INCR", KEYS[1])
  22. redis.call("EXPIRE", KEYS[1], ARGV[2])
  23. return 1
  24. `)
  25. var batchImageDownloadReleaseScript = redis.NewScript(`
  26. local current = tonumber(redis.call("GET", KEYS[1]) or "0")
  27. if current <= 1 then
  28. redis.call("DEL", KEYS[1])
  29. return 0
  30. end
  31. return redis.call("DECR", KEYS[1])
  32. `)
  33. type batchImageDownloadLimiter struct {
  34. rdb *redis.Client
  35. activePrefix string
  36. maxActive int
  37. ttl time.Duration
  38. }
  39. func NewBatchImageDownloadLimiter(rdb *redis.Client, cfg *config.Config) service.BatchImageDownloadLimiter {
  40. maxActive := defaultBatchImageDownloadConcurrency
  41. ttl := defaultBatchImageDownloadActiveTTL
  42. if cfg != nil {
  43. if cfg.BatchImage.MaxDownloadConcurrencyPerUser > 0 {
  44. maxActive = cfg.BatchImage.MaxDownloadConcurrencyPerUser
  45. }
  46. if cfg.BatchImage.MaxDownloadDurationSeconds > 0 {
  47. ttl = time.Duration(cfg.BatchImage.MaxDownloadDurationSeconds) * time.Second
  48. }
  49. }
  50. return &batchImageDownloadLimiter{
  51. rdb: rdb,
  52. activePrefix: defaultBatchImageDownloadActivePrefix,
  53. maxActive: maxActive,
  54. ttl: ttl,
  55. }
  56. }
  57. func (l *batchImageDownloadLimiter) Acquire(ctx context.Context, userID string, kind string) (service.BatchImageDownloadPermit, error) {
  58. if l == nil || l.rdb == nil {
  59. return nil, service.ErrBatchImageDownloadLimited
  60. }
  61. key := l.activeKey(userID)
  62. ok, err := batchImageDownloadAcquireScript.Run(ctx, l.rdb, []string{key}, l.maxActive, int(l.ttl.Seconds())).Int()
  63. if err != nil {
  64. return nil, err
  65. }
  66. if ok != 1 {
  67. return nil, service.ErrBatchImageDownloadLimited
  68. }
  69. return &batchImageDownloadPermit{rdb: l.rdb, key: key}, nil
  70. }
  71. func (l *batchImageDownloadLimiter) activeKey(userID string) string {
  72. return l.activePrefix + userID
  73. }
  74. type batchImageDownloadPermit struct {
  75. rdb *redis.Client
  76. key string
  77. once sync.Once
  78. err error
  79. }
  80. func (p *batchImageDownloadPermit) Release(ctx context.Context) error {
  81. if p == nil || p.rdb == nil || p.key == "" {
  82. return nil
  83. }
  84. p.once.Do(func() {
  85. _, p.err = batchImageDownloadReleaseScript.Run(ctx, p.rdb, []string{p.key}).Result()
  86. })
  87. return p.err
  88. }
  89. var _ service.BatchImageDownloadLimiter = (*batchImageDownloadLimiter)(nil)
  90. var _ service.BatchImageDownloadPermit = (*batchImageDownloadPermit)(nil)