| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 |
- using System.Collections.Concurrent;
- namespace molilian.core
- {
- public static class SpecialBusinessRateLimiter
- {
- private static readonly ConcurrentDictionary<string, SlidingWindowState> States = new();
- public static bool TryAcquire(string bucketKey, int limit, TimeSpan window)
- {
- if (string.IsNullOrWhiteSpace(bucketKey)) return true;
- if (limit <= 0 || window <= TimeSpan.Zero) return true;
- var state = States.GetOrAdd(bucketKey, _ => new SlidingWindowState());
- var nowTicks = DateTime.UtcNow.Ticks;
- var minTicks = nowTicks - window.Ticks;
- lock (state.SyncRoot)
- {
- while (state.Timestamps.Count > 0 && state.Timestamps.Peek() < minTicks)
- {
- state.Timestamps.Dequeue();
- }
- if (state.Timestamps.Count >= limit)
- {
- return false;
- }
- state.Timestamps.Enqueue(nowTicks);
- return true;
- }
- }
- /// <summary>
- /// 归还 acquiredSinceTicks 之后占用的一个名额(请求未真正发生时使用)。
- /// 超过窗口时间的占用已自然释放,不再处理,避免误删其他请求的名额。
- /// </summary>
- public static void Release(string bucketKey, TimeSpan window, long acquiredSinceTicks)
- {
- if (string.IsNullOrWhiteSpace(bucketKey)) return;
- if (!States.TryGetValue(bucketKey, out var state)) return;
- if (DateTime.UtcNow.Ticks - acquiredSinceTicks >= window.Ticks) return;
- lock (state.SyncRoot)
- {
- var timestamps = state.Timestamps.ToArray();
- int index = Array.FindLastIndex(timestamps, t => t >= acquiredSinceTicks);
- if (index < 0) return;
- state.Timestamps.Clear();
- for (int i = 0; i < timestamps.Length; i++)
- {
- if (i != index) state.Timestamps.Enqueue(timestamps[i]);
- }
- }
- }
- private sealed class SlidingWindowState
- {
- public object SyncRoot { get; } = new();
- public Queue<long> Timestamps { get; } = new();
- }
- }
- }
|