.net C# 流量限制令牌桶算法工具类

发布时间 2023-10-09 17:51:14作者: 你好创造者

流量限制令牌桶算法工具类

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace Common
{
    /// <summary>
    /// 令牌桶算法工具类
    /// </summary>
    public class TokenBucket
    {
        /// <summary>
        /// 令牌桶队列
        /// </summary>
        private readonly Queue<int> _tokens = new Queue<int>();

        /// <summary>
        /// 令牌桶容量(最大令牌数)
        /// </summary>
        private readonly int _bucketSize;

        /// <summary>
        /// 初始化令牌桶集合间隔(秒)
        /// </summary>
        public int BucketSize => _bucketSize;

        /// <summary>
        /// 初始化令牌桶集合间隔(秒)
        /// </summary>
        private readonly int _tokensPerSecond;

        /// <summary>
        /// 时间间隔监听器
        /// </summary>
        private readonly Stopwatch _stopwatch = new Stopwatch();

        /// <summary>
        /// 令牌桶
        /// </summary>
        /// <param name="bucketSize">令牌桶容量(默认:512KB)</param>
        /// <param name="tokensPerSecond">初始化令牌桶集合间隔(默认:1秒)</param>
        public TokenBucket(int bucketSize = 512 * 1024, int tokensPerSecond = 1)
        {
            _bucketSize = bucketSize;
            _tokensPerSecond = tokensPerSecond;
        }

        /// <summary>
        /// 请求口令
        /// </summary>
        /// <param name="count">请求分配令牌个数</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>已分配口令个数</returns>
        public async Task<int> GetToken(int count, CancellationToken cancellationToken = default)
        {
            //启动监听
            if (!_stopwatch.IsRunning)
            {
                _stopwatch.Start();
            }
            //重新监听
            else if (_stopwatch.ElapsedMilliseconds >= _tokensPerSecond * 1000)
            {
                _stopwatch.Reset();
            }

            //初始化令牌桶
            while (_tokens.Count < _bucketSize && _stopwatch.ElapsedMilliseconds < _tokensPerSecond * 1000)
            {
                _tokens.Enqueue(1);
            }

            if (_tokens.Count > 0)
            {
                //分配口令
                for (int i = 0; i < count; i++)
                {
                    //如果无可用口令,等待下一轮初始化口令
                    if (_tokens.Count == 0)
                    {
                        _stopwatch.Stop();
                        await Task.Delay(_tokensPerSecond * 1000 - (int)_stopwatch.ElapsedMilliseconds, cancellationToken);

                        //返回已分配口令个数
                        return i;
                    }
                    _tokens.Dequeue();
                }

                return count;
            }
            else
            {
                return 0;
            }
        }

        /// <summary>
        /// 使用令牌
        /// </summary>
        /// <param name="count">使用的令牌数,如果大于最大容量,则循环分配令牌</param>
        /// <param name="func">令牌分配委托,参数1:待分配索引,参数2:已分配口令个数</param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task UseToken(int count, Func<int, int, Task> func, CancellationToken cancellationToken = default)
        {
            int i = 0;
            int t = count;

            while (t > 0)
            {
                int j;
                if ((j = await GetToken(t, cancellationToken)) > 0)
                {
                    await func(i, j);

                    t -= j;
                    i += j;
                }
            }
        }
    }
}

  用法:

await tokenBucket.UseToken(buffer.Length, new Func<int, int, Task>(async (i, j) =>
{
    await wirteStream.WriteAsync(bytes.Slice(i, j), cancellationToken);
}), cancellationToken);