简体   繁体   English

RateGate 类到 Polly 策略

[英]RateGate class to Polly policy

I'm trying to replace the RateGate logic with a Polly policy.我正在尝试用 Polly 策略替换 RateGate 逻辑。 However, there is no status code or anything and I'm not sure if it's possible achieve the same idea but with Polly.但是,没有状态代码或任何东西,我不确定是否可以通过 Polly 实现相同的想法。

Usage用法

// Binance allows 5 messages per second, but we still get rate limited if we send a lot of messages at that rate
// By sending 3 messages per second, evenly spaced out, we can keep sending messages without being limited
private readonly RateGate _webSocketRateLimiter = new RateGate(1, TimeSpan.FromMilliseconds(330));

private void Send(IWebSocket webSocket, object obj)
{
    var json = JsonConvert.SerializeObject(obj);

    _webSocketRateLimiter.WaitToProceed();

    Log.Trace("Send: " + json);

    webSocket.Send(json);
}

RateGate class RateGate 类

public class RateGate : IDisposable
{
    // Semaphore used to count and limit the number of occurrences per
    // unit time.
    private readonly SemaphoreSlim _semaphore;

    // Times (in millisecond ticks) at which the semaphore should be exited.
    private readonly ConcurrentQueue<int> _exitTimes;

    // Timer used to trigger exiting the semaphore.
    private readonly Timer _exitTimer;

    // Whether this instance is disposed.
    private bool _isDisposed;

    /// <summary>
    /// Number of occurrences allowed per unit of time.
    /// </summary>
    public int Occurrences
    {
        get; private set;
    }

    /// <summary>
    /// The length of the time unit, in milliseconds.
    /// </summary>
    public int TimeUnitMilliseconds
    {
        get; private set;
    }

    /// <summary>
    /// Flag indicating we are currently being rate limited
    /// </summary>
    public bool IsRateLimited
    {
        get { return !WaitToProceed(0); }
    }

    /// <summary>
    /// Initializes a <see cref="RateGate"/> with a rate of <paramref name="occurrences"/>
    /// per <paramref name="timeUnit"/>.
    /// </summary>
    /// <param name="occurrences">Number of occurrences allowed per unit of time.</param>
    /// <param name="timeUnit">Length of the time unit.</param>
    /// <exception cref="ArgumentOutOfRangeException">
    /// If <paramref name="occurrences"/> or <paramref name="timeUnit"/> is negative.
    /// </exception>
    public RateGate(int occurrences, TimeSpan timeUnit)
    {
        // Check the arguments.
        if (occurrences <= 0)
            throw new ArgumentOutOfRangeException(nameof(occurrences), "Number of occurrences must be a positive integer");
        if (timeUnit != timeUnit.Duration())
            throw new ArgumentOutOfRangeException(nameof(timeUnit), "Time unit must be a positive span of time");
        if (timeUnit >= TimeSpan.FromMilliseconds(UInt32.MaxValue))
            throw new ArgumentOutOfRangeException(nameof(timeUnit), "Time unit must be less than 2^32 milliseconds");

        Occurrences = occurrences;
        TimeUnitMilliseconds = (int)timeUnit.TotalMilliseconds;

        // Create the semaphore, with the number of occurrences as the maximum count.
        _semaphore = new SemaphoreSlim(Occurrences, Occurrences);

        // Create a queue to hold the semaphore exit times.
        _exitTimes = new ConcurrentQueue<int>();

        // Create a timer to exit the semaphore. Use the time unit as the original
        // interval length because that's the earliest we will need to exit the semaphore.
        _exitTimer = new Timer(ExitTimerCallback, null, TimeUnitMilliseconds, -1);
    }

    // Callback for the exit timer that exits the semaphore based on exit times
    // in the queue and then sets the timer for the nextexit time.
    // Credit to Jim: http://www.jackleitch.net/2010/10/better-rate-limiting-with-dot-net/#comment-3620
    // for providing the code below, fixing issue #3499 - https://github.com/QuantConnect/Lean/issues/3499
    private void ExitTimerCallback(object state)
    {
        try
        {
            // While there are exit times that are passed due still in the queue,
            // exit the semaphore and dequeue the exit time.
            var exitTime = 0;
            var exitTimeValid = _exitTimes.TryPeek(out exitTime);
            while (exitTimeValid)
            {
                if (unchecked(exitTime - Environment.TickCount) > 0)
                {
                    break;
                }
                _semaphore.Release();
                _exitTimes.TryDequeue(out exitTime);
                exitTimeValid = _exitTimes.TryPeek(out exitTime);
            }
            // we are already holding the next item from the queue, do not peek again
            // although this exit time may have already pass by this stmt.
            var timeUntilNextCheck = exitTimeValid
                ? Math.Min(TimeUnitMilliseconds, Math.Max(0, exitTime - Environment.TickCount))
                : TimeUnitMilliseconds;

            _exitTimer.Change(timeUntilNextCheck, -1);
        }
        catch (Exception)
        {
            // can throw if called when disposing
        }
    }

    /// <summary>
    /// Blocks the current thread until allowed to proceed or until the
    /// specified timeout elapses.
    /// </summary>
    /// <param name="millisecondsTimeout">Number of milliseconds to wait, or -1 to wait indefinitely.</param>
    /// <returns>true if the thread is allowed to proceed, or false if timed out</returns>
    public bool WaitToProceed(int millisecondsTimeout)
    {
        // Check the arguments.
        if (millisecondsTimeout < -1)
            throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));

        CheckDisposed();

        // Block until we can enter the semaphore or until the timeout expires.
        var entered = _semaphore.Wait(millisecondsTimeout);

        // If we entered the semaphore, compute the corresponding exit time
        // and add it to the queue.
        if (entered)
        {
            var timeToExit = unchecked(Environment.TickCount + TimeUnitMilliseconds);
            _exitTimes.Enqueue(timeToExit);
        }

        return entered;
    }

    /// <summary>
    /// Blocks the current thread until allowed to proceed or until the
    /// specified timeout elapses.
    /// </summary>
    /// <param name="timeout"></param>
    /// <returns>true if the thread is allowed to proceed, or false if timed out</returns>
    public bool WaitToProceed(TimeSpan timeout)
    {
        return WaitToProceed((int)timeout.TotalMilliseconds);
    }

    /// <summary>
    /// Blocks the current thread indefinitely until allowed to proceed.
    /// </summary>
    public void WaitToProceed()
    {
        WaitToProceed(Timeout.Infinite);
    }

    // Throws an ObjectDisposedException if this object is disposed.
    private void CheckDisposed()
    {
        if (_isDisposed)
            throw new ObjectDisposedException("RateGate is already disposed");
    }

    /// <summary>
    /// Releases unmanaged resources held by an instance of this class.
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases unmanaged resources held by an instance of this class.
    /// </summary>
    /// <param name="isDisposing">Whether this object is being disposed.</param>
    protected virtual void Dispose(bool isDisposing)
    {
        if (!_isDisposed)
        {
            if (isDisposing)
            {
                // The semaphore and timer both implement IDisposable and
                // therefore must be disposed.
                _semaphore.Dispose();
                _exitTimer.Dispose();

                _isDisposed = true;
            }
        }
    }
}

GitHub source code GitHub源代码

Rate gate费率门

Disclaimer : I haven't used this component so what I describe here is what I understand from the code.免责声明:我没有使用过这个组件,所以我在这里描述的是我从代码中理解的内容。

It is an intrusive policy which means it modifies/alters the execution/data flow in order to slow down fast producer or smoothen out burst.这是一种侵入性策略,这意味着它修改/更改执行/数据流以减慢快速生产者或平滑突发。 It is blocking the flow to avoid resource abuse.它正在阻止流程以避免资源滥用。

Here you can specify the "sleep duration" between subsequent calls which is enforced by the gate itself.在这里,您可以指定由门本身强制执行的后续调用之间的“睡眠持续时间”。

Polly's Rate limiter Polly 的速率限制器

This policy is designed to avoid resource abuse as well.该政策也旨在避免资源滥用。 That means if the consumer issues too many requests against the resource under a predefined time then it simply shortcuts the execution by throwing a RateLimitRejectedException .这意味着如果消费者在预定义的时间内对资源发出太多请求,那么它只会通过抛出RateLimitRejectedException来缩短执行。

So, if you want to allow 20 executions under 1 minute所以,如果你想在 1 分钟内允许 20 次执行

RateLimitPolicy rateLimiter = Policy
    .RateLimit(20, TimeSpan.FromSeconds(1));

and you do not want to exceed the limit you have to wait by yourself你不想超过你必须自己等待的限制

rateLimiter.Execute(() =>
{
    //Your Action delegate which runs <1ms
    Thread.Sleep(50);
});

So, the executions should be distributed evenly during the allowed period.因此,执行应在允许的时间内平均分配。 If your manually injected delay is shorter let's say 10ms then it will throw an exception.如果您手动注入的延迟更短,比如说 10 毫秒,那么它将引发异常。

Conclusion结论

According to my understanding both works like a proxy object.据我了解,两者都像代理对象一样工作。 They are sitting between the consumer and producer to control the consumption rate.他们坐在消费者和生产者之间,以控制消费率。

The rate gate does that by injecting artificial delays whereas the rate limiter shortcuts the execution if abuse is detected.速率门通过注入人为延迟来做到这一点,而速率限制器会在检测到滥用时缩短执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM