• 常见的五种服务限流算法及其实现


      常见的五种限流算法可简单概括为“两窗两漏一令牌”,下面将进行详细介绍:

    1. 固定窗口算法

    介绍

    固定时间周期划分时间为多个时间窗口,如:每10秒为一个时间窗口。

    在每个时间窗口内,每有一个请求,计数器加一。

    当计数器超过限制,丢弃本窗口之后的所有请求。

    当下一时间窗口开始,重置计数器。

    优点

    原理简单,固定窗口计数。

    缺点 无法处理前后密集型请求,例如每秒限制100次,前最后一秒的10ms请求100次,后最后一秒的前10ms请求100次,相当于200次/0.02s。
    图例
    示例  
    实现
    struct FixedWindow
    {
        int64_t lLastNo;    // 上一次No(记录不同固定窗口区间标识);
        int64_t lRemainReq;  // 剩余req
    };
    typedef std::map<std::string, FixedWindow> FixedWindowMap;
    
    class FixedWindowManager
    {
    private:
        FixedWindowManager() {}
        virtual ~FixedWindowManager() {}
    
        FixedWindowManager(const FixedWindowManager&) = delete;
        FixedWindowManager& operator = (const FixedWindowManager&) = delete;
    
        using MutexLockUnique = std::unique_lock<std::mutex>;
    
    public:
        static FixedWindowManager* GetInstance()
        {
            static FixedWindowManager manager;
            return &manager;
        }
    
        bool tryAcquire(const std::string& strKey, int32_t& lMilSec, 
            const uint64_t lReqNum, const uint32_t lFixedMs, const uint64_t lMaxReq)
        {
            MutexLockUnique lock(m_mutexFixedWindow);
            lMilSec = -1;
            uint64_t lCurrentMilSec = AbstractRateLimiter::getCurrentMilSec();
            uint64_t lFixedWindowsNo = lCurrentMilSec / lFixedMs;
    
            FixedWindowMap::iterator it = m_mapFixedWindow.find(strKey);
            if (it == m_mapFixedWindow.end())
            {
                FixedWindow FixedWindow;
                FixedWindow.lLastNo = lFixedWindowsNo;
                FixedWindow.lRemainReq = lMaxReq - lReqNum;
                m_mapFixedWindow[strKey] = FixedWindow;
    
                return true;
            }
    
    #if 0
            printf("CurrentMs[%lld], CurrentNo[%lld], LastNo[%lld], RemainReq[%lld] \n",
                lCurrentMilSec, lFixedWindowsNo, it->second.lLastNo, it->second.lRemainReq);
    #endif
            
            if (lFixedWindowsNo == it->second.lLastNo)
            {
                // 在一个固定窗口内
                if (it->second.lRemainReq >= lReqNum)
                {
                    it->second.lRemainReq -= lReqNum;
                    return true;
                }
                else
                {
                    // 计算下一个最近窗口的时间间隔;
                    lMilSec = (lFixedWindowsNo + 1) * lFixedMs - lCurrentMilSec;
                    return false;
                }
            }
    
            it->second.lLastNo = lFixedWindowsNo;
            it->second.lRemainReq = lMaxReq - lReqNum;
    
            return true;
        }
    private:
        FixedWindowMap m_mapFixedWindow;
        mutable std::mutex    m_mutexFixedWindow;
    };
    
    #define AflGetFixedWindowManager FixedWindowManager::GetInstance
    View Code

    2. 滑动窗口算法

    介绍

    以当前时间为截止时间,往前取一定的时间作为时间窗口,比如:往前取 10s 的时间

    当有新的请求进入时,删除时间窗口之外的请求,对时间窗口之内的请求进行计数统计,若未超过限制,则进行放行操作;若超过限制,则拒绝本次服务。

    优点

    有效处理固定窗口的突发缺点。

    缺点 当时间区越长、精度越高,占用的空间资源就越大。
    图例
    示例  
    实现
    
    
    /* key: ms, value: usereq */
    typedef std::map<int64_t, int64_t> SlideWindow;
    typedef std::map<std::string, SlideWindow> SlideWindowMap;
    
    class SlideWindowManager
    {
    private:
        SlideWindowManager() {}
        virtual ~SlideWindowManager() {}
    
        SlideWindowManager(const SlideWindowManager&) = delete;
        SlideWindowManager& operator = (const SlideWindowManager&) = delete;
    
        using MutexLockUnique = std::unique_lock<std::mutex>;
    
    public:
        static SlideWindowManager* GetInstance()
        {
            static SlideWindowManager manager;
            return &manager;
        }
    
        bool tryAcquire(const std::string& strKey, int32_t& lMilSec,
            const uint64_t lReqNum, const uint32_t lSlideMs, const uint64_t lMaxReq)
        {
            MutexLockUnique lock(m_mutexSlideWindow);
            lMilSec = -1;
            uint64_t lCurrentMilSec = AbstractRateLimiter::getCurrentMilSec();
    
            SlideWindowMap::iterator it = m_mapSlideWindow.find(strKey);
            if (it == m_mapSlideWindow.end())
            {
                SlideWindow& slide = m_mapSlideWindow[strKey];
                slide[lCurrentMilSec] = lReqNum;
                return true;
            }
    
            SlideWindow& slide = it->second;
            uint64_t lUseReq = 0;
            for (SlideWindow::iterator itWin = slide.begin(); itWin != slide.end(); )
            {
                if ((lCurrentMilSec - itWin->first) >= lSlideMs)
                {
                    // 滑动窗口过期;
                    itWin = slide.erase(itWin);
                }
                else
                {
                    lUseReq += itWin->second;
                    itWin++;
                }
            }
    
            if ((lMaxReq - lUseReq) >= lReqNum)
            {
                SlideWindow::iterator itTemp = slide.find(lCurrentMilSec);
                if (itTemp == slide.end())
                {
                    slide[lCurrentMilSec] = 0;
                }
                slide[lCurrentMilSec] += lReqNum;
                return true;
            }
    
    #if 0
            printf("CurrentMs[%lld], UseReq[%lld] \n", lCurrentMilSec, lUseReq);
    #endif
    
            // 未来需要等待释放的请求数;
            uint64_t lWaitReq = lReqNum - (lMaxReq - lUseReq);
            for (SlideWindow::iterator itWin = slide.begin(); itWin != slide.end(); )
            {
                lWaitReq -= itWin->second;
                if (lWaitReq <= 0)
                {
                    lMilSec = itWin->first + lSlideMs - lCurrentMilSec;
                    break;
                }
            }
    
            return false;
        }
    
    private:
        SlideWindowMap m_mapSlideWindow;
        mutable std::mutex    m_mutexSlideWindow;
    };
    
    #define AflGetSlideWindowManager SlideWindowManager::GetInstance
    View Code

    3. 漏桶算法

    介绍

    将每个请求视为水滴加入漏桶进行存储

    漏桶以固定速率匀速出水(处理请求)

    若桶满则抛弃请求

    优点

    限流的绝对平均化。

    缺点  不适合突发请求场景、请求延迟高:当短时间内有大量的突发请求时,即便此时服务器没有任何负载,每个请求也都得在队列中等待一段时间才能被响应。
    图例
    示例  
    实现  
    using MutexLockUnique = std::unique_lock<std::mutex>;
    
    class LeakyBucket
    {
    public: 
        explicit LeakyBucket(uint64_t iMaxSize, uint64_t iTakeMs) : m_lMaxSize(iMaxSize), m_lTakeMs(iTakeMs), m_lLastMs(-1) { }
        ~LeakyBucket() { }
    
        bool put(const uint64_t& x)
        {
            MutexLockUnique lock(m_mutex);
            if (m_queue.size() >= m_lMaxSize) { return false; }
    
            m_queue.push_back(x);
            m_notEmpty.notify_one();
            return true;
        }
    
        uint64_t take()
        {
            MutexLockUnique lock(m_mutex);
            m_notEmpty.wait(lock, [this] {  return !(this->m_queue.empty()); });
            assert(!m_queue.empty());
    
            int64_t lWaitMs = m_lTakeMs - (AbstractRateLimiter::getCurrentMilSec() - m_lLastMs);
            if (lWaitMs > 0)
            {
                AbstractRateLimiter::myMSleep(lWaitMs);
            }
            m_lLastMs = AbstractRateLimiter::getCurrentMilSec();
    
            uint64_t front(std::move(m_queue.front()));
            m_queue.pop_front();
            return front;
        }
    
    private:
        int64_t                    m_lMaxSize;
        std::condition_variable m_notEmpty;
    
        mutable std::mutex        m_mutex;
        std::deque<uint64_t>    m_queue;
    
        int64_t                    m_lLastMs;    // 上一次take ms
        int64_t                    m_lTakeMs;    // take间隔 ms
    };
    typedef std::map<std::string, LeakyBucket*> LeakyBucketMap;
    
    class LeakyBucketManager
    {
    private:
        LeakyBucketManager() {}
        virtual ~LeakyBucketManager() {}
    
        LeakyBucketManager(const LeakyBucketManager&) = delete;
        LeakyBucketManager& operator = (const LeakyBucketManager&) = delete;
    
    public:
        static LeakyBucketManager* GetInstance()
        {
            static LeakyBucketManager manager;
            return &manager;
        }
    
        bool tryAcquire(const std::string& strKey, int32_t& lMilSec,
            const uint64_t lTokenNum, const uint64_t lCapacityNum, const double dRate)
        {
            MutexLockUnique lock(m_mutexLeakyBucket);
            lMilSec = -1;
            uint64_t lCurrentMilSec = AbstractRateLimiter::getCurrentMilSec();
    
            LeakyBucketMap::iterator it = m_mapLeakyBucket.find(strKey);
            if (it == m_mapLeakyBucket.end())
            {
                LeakyBucket* pLeakyBucket = new LeakyBucket(lCapacityNum, (1000 / dRate));
                m_mapLeakyBucket[strKey] = pLeakyBucket;
    
                it = m_mapLeakyBucket.find(strKey);
                if (it == m_mapLeakyBucket.end())
                {
                    return false;
                }
            }
            
            if (!it->second->put(1)) { return false; }
            it->second->take();
    
            return true;
        }
    
    private:
        LeakyBucketMap m_mapLeakyBucket;
        mutable std::mutex    m_mutexLeakyBucket;
    };
    
    #define AflGetLeakyBucketManager LeakyBucketManager::GetInstance
    View Code

    4. 漏斗算法

    介绍

    漏斗算法是《Redis深度历险》中提到的一种限流方案。漏斗有一定的容量,并且以一定速率漏水,漏斗的剩余空间即允许请求的空间。

    漏斗算法的模型和漏桶算法在模型上是一致的,容器叫法不同,一个叫漏斗,一个叫漏桶,剩余空间直接决定了请求是否可以通过,只不过在漏斗算法中,一旦通过,请求便可以立即访问;

    而漏桶算法中,请求通过后,会被暂存在容器中,等待被匀速处理,两者的差别即在于此。

    优点

    预热限流和平滑限流兼备。

    缺点  
    图例
    示例  
    实现  
    using MutexLockUnique = std::unique_lock<std::mutex>;
    
    class LeakyPipe
    {
    public:
        explicit LeakyPipe(uint64_t lCapacity, uint64_t iTakeMs) : m_lCapacity(lCapacity), m_lReamin(lCapacity), m_lTakeMs(iTakeMs), m_lLastMs(-1) { }
        ~LeakyPipe() { }
    
        bool put(const uint64_t& x)
        {
            MutexLockUnique lock(m_mutex);
            if (m_queue.size() >= m_lCapacity) { return false; }
    
            m_queue.push_back(x);
            m_notEmpty.notify_one();
            return true;
        }
    
        uint64_t take()
        {
            MutexLockUnique lock(m_mutex);
            m_notEmpty.wait(lock, [this] {  return !(this->m_queue.empty()); });
            assert(!m_queue.empty());
    
            // 计算剩余空间;
            m_lReamin += (AbstractRateLimiter::getCurrentMilSec() - m_lLastMs) / m_lTakeMs;
            if (m_lReamin > m_lCapacity) { m_lReamin = m_lCapacity; }
    
            if (m_lReamin <= 0)
            {
                int64_t lWaitMs = m_lTakeMs - (AbstractRateLimiter::getCurrentMilSec() - m_lLastMs);
                if (lWaitMs > 0)
                {
                    AbstractRateLimiter::myMSleep(lWaitMs);
                }
                ++m_lReamin;
            }
            --m_lReamin;
            m_lLastMs = AbstractRateLimiter::getCurrentMilSec();
    
            uint64_t front(std::move(m_queue.front()));
            m_queue.pop_front();
            return front;
        }
    
    private:
        std::condition_variable m_notEmpty;
        mutable std::mutex        m_mutex;
        std::deque<uint64_t>    m_queue;
    
        int64_t                    m_lLastMs;        // 上一次take ms
        int64_t                    m_lTakeMs;        // take间隔 ms
        int64_t                    m_lCapacity;    // 容量;
        int64_t                    m_lReamin;        // 剩余容量;
    };
    typedef std::map<std::string, LeakyPipe*> LeakyPipeMap;
    
    class LeakyPipeManager
    {
    private:
        LeakyPipeManager() {}
        virtual ~LeakyPipeManager() {}
    
        LeakyPipeManager(const LeakyPipeManager&) = delete;
        LeakyPipeManager& operator = (const LeakyPipeManager&) = delete;
    
    public:
        static LeakyPipeManager* GetInstance()
        {
            static LeakyPipeManager manager;
            return &manager;
        }
    
        bool tryAcquire(const std::string& strKey, int32_t& lMilSec,
            const uint64_t lTokenNum, const uint64_t lCapacityNum, const double dRate)
        {
            MutexLockUnique lock(m_mutexLeakyPipe);
            lMilSec = -1;
            uint64_t lCurrentMilSec = AbstractRateLimiter::getCurrentMilSec();
    
            LeakyPipeMap::iterator it = m_mapLeakyPipe.find(strKey);
            if (it == m_mapLeakyPipe.end())
            {
                LeakyPipe* pLeakyPipe = new LeakyPipe(lCapacityNum, (1000 / dRate));
                m_mapLeakyPipe[strKey] = pLeakyPipe;
    
                it = m_mapLeakyPipe.find(strKey);
                if (it == m_mapLeakyPipe.end())
                {
                    return false;
                }
            }
    
            if (!it->second->put(1)) { return false; }
            it->second->take();
    
            return true;
        }
    
    private:
        LeakyPipeMap m_mapLeakyPipe;
        mutable std::mutex    m_mutexLeakyPipe;
    };
    
    #define AflGetLeakyPipeManager LeakyPipeManager::GetInstance
    View Code

    5. 令牌桶算法

    介绍

    以恒定的速度往令牌桶中放入令牌

    当有请求过来则从令牌桶中获取令牌进行后续请求

    当获取令牌失败后则进行友好处理。

    优点

    预热限流和平滑限流兼备。

    缺点  
    图例
    示例  
    实现
    struct TokenBucket
    {
        int64_t lLastMs;        // 上一次ms;
        int64_t lRemainToken;    // 剩余token
    };
    typedef std::map<std::string, TokenBucket> TokenBucketMap;
    
    class TokenBucketManager
    {
    private:
        TokenBucketManager() {}
        virtual ~TokenBucketManager() {}
    
        TokenBucketManager(const TokenBucketManager&) = delete;
        TokenBucketManager& operator = (const TokenBucketManager&) = delete;
    
        using MutexLockUnique = std::unique_lock<std::mutex>;
    
    public:
        static TokenBucketManager* GetInstance()
        {
            static TokenBucketManager manager;
            return &manager;
        }
    
        bool tryAcquire(const std::string& strKey, int32_t& lMilSec, 
            const uint64_t lTokenNum, const uint64_t lMaxTokenNum, const double dSpeed)
        {
            MutexLockUnique lock(m_mutexTokenBucket);
            lMilSec = -1;
            uint64_t lCurrentMilSec = AbstractRateLimiter::getCurrentMilSec();
    
            TokenBucketMap::iterator it = m_mapTokenBucket.find(strKey);
            if (it == m_mapTokenBucket.end())
            {
                TokenBucket tokenBucket;
                tokenBucket.lLastMs = lCurrentMilSec;
                tokenBucket.lRemainToken = lMaxTokenNum - lTokenNum;
                m_mapTokenBucket[strKey] = tokenBucket;
    
                return true;
            }
    
    #if 0
            printf("CurrentMs[%lld], LastMs[%lld], RemainToken[%lld] \n",
                lCurrentMilSec, it->second.lLastMs, it->second.lRemainToken);
    #endif
            
            uint64_t lDiffTime = lCurrentMilSec - it->second.lLastMs;
            double dOneTime = 1000.0 / dSpeed;    // 一个令牌的填充时间
            if (lDiffTime > dOneTime)
            {
                // 如果间隔大于了一个令牌的填充时间 则进行填充;
                uint64_t lNowRemain = it->second.lRemainToken + (lDiffTime / dOneTime);
                if (lNowRemain >= lMaxTokenNum)
                {
                    it->second.lLastMs = lCurrentMilSec;
                    it->second.lRemainToken = lMaxTokenNum - lTokenNum;
    
                    return true;
                }
                else
                {
                    it->second.lLastMs = it->second.lLastMs + (uint64_t(lDiffTime / dOneTime) * dOneTime);
                    it->second.lRemainToken = lNowRemain;
                }
            }
    
            if (it->second.lRemainToken >= lTokenNum)
            {
                it->second.lRemainToken -= lTokenNum;
                return true;
            }
    
            // 计算下一次满足令牌的时间
            uint64_t lNextTime = it->second.lLastMs + (lTokenNum - it->second.lRemainToken) * dOneTime;
            
            lMilSec = lNextTime - lCurrentMilSec;
            return false;
        }
    
    private:
        TokenBucketMap m_mapTokenBucket;
        mutable std::mutex    m_mutexTokenBucket;
    };
    
    #define AflGetTokenBucketManager TokenBucketManager::GetInstance
    View Code

    以上均为单进程的服务限流,主要目的是了解五种常见限流算法及其优缺点,详细参见gitee项目:https://gitee.com/ma_you_sun0821vip/RateLimiter

    后续会补充分布式的服务限流,敬请期待!

  • 相关阅读:
    JAVA内存管理
    计算机网络面试题一
    计算机网络面试题二
    【有容云干货-容器系列】补脑专用,容器生态圈脑图大放送
    PPT | Docker定义存储-让应用无痛运行
    有容云-容器安全,六招解决
    新IT运维时代 | Docker运维之最佳实践-下篇
    新IT运维时代 | Docker运维之最佳实践-上篇
    有容云-【原理】Docker存储驱动之AUFS
    【有容云案例系列】基于Jenkins和Kubernetes的CI工作流
  • 原文地址:https://www.cnblogs.com/Kingfans/p/16263774.html
Copyright © 2020-2023  润新知