단순 이관
This commit is contained in:
71
Bitget_Server/Infrastructure/Bitget/BitgetRateLimiter.cs
Normal file
71
Bitget_Server/Infrastructure/Bitget/BitgetRateLimiter.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
namespace Bitget_Server.Infrastructure.Bitget;
|
||||
|
||||
/// <summary>
|
||||
/// Bitget REST API에 대한 클라이언트 사이드 Rate Limiter.
|
||||
/// Sliding window 방식으로 1초당 N회 호출 제한을 강제한다.
|
||||
/// 모든 외부 호출은 await AcquireAsync(...) 후에 수행되어야 한다.
|
||||
/// </summary>
|
||||
public class BitgetRateLimiter
|
||||
{
|
||||
private readonly int _maxRequestsPerSecond;
|
||||
private readonly Queue<long> _requestTicks = new();
|
||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||
private readonly ILogger<BitgetRateLimiter> _logger;
|
||||
|
||||
public BitgetRateLimiter(int maxRequestsPerSecond, ILogger<BitgetRateLimiter> logger)
|
||||
{
|
||||
// 0 또는 음수 → 무제한 모드. 양수 → 슬라이딩 윈도우 적용.
|
||||
_maxRequestsPerSecond = maxRequestsPerSecond <= 0 ? int.MaxValue : maxRequestsPerSecond;
|
||||
_logger = logger;
|
||||
|
||||
if (_maxRequestsPerSecond == int.MaxValue)
|
||||
_logger.LogWarning("BitgetRateLimiter: RateLimitPerSecond <= 0 — 무제한 모드로 동작합니다.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 호출 슬롯이 확보될 때까지 대기한 뒤 즉시 반환한다.
|
||||
/// 무제한 모드(_maxRequestsPerSecond == int.MaxValue) 에서는 즉시 반환.
|
||||
/// </summary>
|
||||
public async Task AcquireAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_maxRequestsPerSecond == int.MaxValue) return;
|
||||
|
||||
await _gate.WaitAsync(ct).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
int totalWaitedMs = 0;
|
||||
while (true)
|
||||
{
|
||||
var now = Environment.TickCount64;
|
||||
// 1초 이전 요청 기록 제거
|
||||
while (_requestTicks.Count > 0 && now - _requestTicks.Peek() >= 1000)
|
||||
_requestTicks.Dequeue();
|
||||
|
||||
if (_requestTicks.Count < _maxRequestsPerSecond)
|
||||
{
|
||||
_requestTicks.Enqueue(now);
|
||||
if (totalWaitedMs >= 100)
|
||||
_logger.LogInformation("Rate limit throttled {WaitMs}ms (queue={Count}/{Max})",
|
||||
totalWaitedMs, _requestTicks.Count, _maxRequestsPerSecond);
|
||||
return;
|
||||
}
|
||||
|
||||
// 가장 오래된 요청이 1초 경과할 때까지 대기
|
||||
var waitMs = 1000 - (int)(now - _requestTicks.Peek()) + 1;
|
||||
if (waitMs < 1) waitMs = 1;
|
||||
totalWaitedMs += waitMs;
|
||||
|
||||
_logger.LogDebug("Rate limit hit. Waiting {WaitMs}ms (queue={Count}/{Max})",
|
||||
waitMs, _requestTicks.Count, _maxRequestsPerSecond);
|
||||
|
||||
_gate.Release();
|
||||
try { await Task.Delay(waitMs, ct).ConfigureAwait(false); }
|
||||
finally { await _gate.WaitAsync(ct).ConfigureAwait(false); }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_gate.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user