• 互联网应对高峰流量控制- 漏桶算法和令牌桶算法(滴滴面试)


    天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的接口也需要安装上“保险丝”,以防止非预期的请求对系统压力过大而引起的系统瘫痪,当流量过大时,可以采取拒绝或者引流等机制。 

    二、常用的限流算法

          常用的限流算法有两种:漏桶算法和令牌桶算法

    1、漏桶算法

          漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

    图1 漏桶算法示意图

    在某些情况下,漏桶算法不能够有效地使用网络资源。因为漏桶的漏出速率是固定的参数,所以即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使某一个单独的流突发到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。通常,漏桶算法与令牌桶算法可以结合起来为网络流量提供更大的控制。

    2、令牌桶算法:

          对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。如图2所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

    图2 令牌桶算法示意图

      令牌桶算法的基本过程如下:

      假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中;

      假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;

      当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络;

      如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外;

      算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。对于在流量限制外的数据包可以以不同的方式处理:

      它们可以被丢弃;

      它们可以排放在队列中以便当令牌桶中累积了足够多的令牌时再传输;

      它们可以继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。

    二、两种算法的区别

    两者主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,所以它适合于具有突发特性的流量。

    三、限流工具类RateLimiter

       Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法来完成限流,非常易于使用。RateLimiter类的接口描述请参考:RateLimiter接口描述,具体使用请参考:RateLimiter使用实践

          下面是主要源码: 

    public double acquire() {
            return acquire(1);
        }
    
     public double acquire(int permits) {
            checkPermits(permits);  //检查参数是否合法(是否大于0)
            long microsToWait;
            synchronized (mutex) { //应对并发情况需要同步
                microsToWait = reserveNextTicket(permits, readSafeMicros()); //获得需要等待的时间 
            }
            ticker.sleepMicrosUninterruptibly(microsToWait); //等待,当未达到限制时,microsToWait为0
            return 1.0 * microsToWait / TimeUnit.SECONDS.toMicros(1L);
        }
    
    private long reserveNextTicket(double requiredPermits, long nowMicros) {
            resync(nowMicros); //补充令牌
            long microsToNextFreeTicket = nextFreeTicketMicros - nowMicros;
            double storedPermitsToSpend = Math.min(requiredPermits, this.storedPermits); //获取这次请求消耗的令牌数目
            double freshPermits = requiredPermits - storedPermitsToSpend;
    
            long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
                    + (long) (freshPermits * stableIntervalMicros); 
    
            this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;
            this.storedPermits -= storedPermitsToSpend; // 减去消耗的令牌
            return microsToNextFreeTicket;
        }
    
    private void resync(long nowMicros) {
            // if nextFreeTicket is in the past, resync to now
            if (nowMicros > nextFreeTicketMicros) {
                storedPermits = Math.min(maxPermits,
                        storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
                nextFreeTicketMicros = nowMicros;
            }
        }

    参考:漏桶算法和令牌桶算法

  • 相关阅读:
    C# 线程手册 第二章 .NET 中的线程系列
    C# 线程手册 第四章 线程设计原则
    C# 线程手册 第四章 线程设计原则 MTA 线程模型
    C# 线程手册 第三章 使用线程 创建线程安全的包装器(实战篇)
    C# 线程手册 第一章 线程定义系列
    C# 线程手册 第三章 使用线程 小心死锁
    C# 线程手册 第四章 线程设计原则 线程及线程间关系
    C# 线程手册 第一章 线程定义 中断和局部线程存储
    C# 线程手册 第三章 使用线程 实现一个数据库连接池(实战篇)
    C# 线程手册 第四章 线程设计原则 对等线程模型
  • 原文地址:https://www.cnblogs.com/aspirant/p/9093437.html
Copyright © 2020-2023  润新知