SpecialBusinessRateLimiter.cs 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. using System.Collections.Concurrent;
  2. namespace molilian.core
  3. {
  4. public static class SpecialBusinessRateLimiter
  5. {
  6. private static readonly ConcurrentDictionary<string, SlidingWindowState> States = new();
  7. public static bool TryAcquire(string bucketKey, int limit, TimeSpan window)
  8. {
  9. if (string.IsNullOrWhiteSpace(bucketKey)) return true;
  10. if (limit <= 0 || window <= TimeSpan.Zero) return true;
  11. var state = States.GetOrAdd(bucketKey, _ => new SlidingWindowState());
  12. var nowTicks = DateTime.UtcNow.Ticks;
  13. var minTicks = nowTicks - window.Ticks;
  14. lock (state.SyncRoot)
  15. {
  16. while (state.Timestamps.Count > 0 && state.Timestamps.Peek() < minTicks)
  17. {
  18. state.Timestamps.Dequeue();
  19. }
  20. if (state.Timestamps.Count >= limit)
  21. {
  22. return false;
  23. }
  24. state.Timestamps.Enqueue(nowTicks);
  25. return true;
  26. }
  27. }
  28. /// <summary>
  29. /// 归还 acquiredSinceTicks 之后占用的一个名额(请求未真正发生时使用)。
  30. /// 超过窗口时间的占用已自然释放,不再处理,避免误删其他请求的名额。
  31. /// </summary>
  32. public static void Release(string bucketKey, TimeSpan window, long acquiredSinceTicks)
  33. {
  34. if (string.IsNullOrWhiteSpace(bucketKey)) return;
  35. if (!States.TryGetValue(bucketKey, out var state)) return;
  36. if (DateTime.UtcNow.Ticks - acquiredSinceTicks >= window.Ticks) return;
  37. lock (state.SyncRoot)
  38. {
  39. var timestamps = state.Timestamps.ToArray();
  40. int index = Array.FindLastIndex(timestamps, t => t >= acquiredSinceTicks);
  41. if (index < 0) return;
  42. state.Timestamps.Clear();
  43. for (int i = 0; i < timestamps.Length; i++)
  44. {
  45. if (i != index) state.Timestamps.Enqueue(timestamps[i]);
  46. }
  47. }
  48. }
  49. private sealed class SlidingWindowState
  50. {
  51. public object SyncRoot { get; } = new();
  52. public Queue<long> Timestamps { get; } = new();
  53. }
  54. }
  55. }