限流算法 | 漏桶算法、令牌桶算法

3,852 阅读3分钟

在系统应对大流量,高并发的访问时,限流算法可以帮助我们控制流量,从而避免系统负载过高而崩溃。接下来将介绍几种常见的限流算法,包括漏桶算法、令牌桶算法、计数器算法、滑动窗口算法和漏斗算法。

漏桶算法

rate-limit1.png

漏桶算法是一种经典的限流算法,它可以用来控制请求速率。漏桶算法的原理是将水放入一个固定容量的桶中,桶底有一个固定大小的洞,水会以固定速率从洞中流出。如果水的流入速度超过了洞口的流出速率,那么多余的水会被丢弃。因此,漏桶算法通常用于限制网络流量、控制数据传输速率等场景,但是无法应对突发的高流量。

利用这个思路,我们可以用这个算法来控制一个系统能接受的访问流量

下面给一个简单的实现
通过这个例子可以看到如何使用漏桶算法来限制流量

class LeakyBucket {
  private water: number = 0 // 水位
  private lastLeakTime: number = Date.now();

  constructor(private readonly capacity: number, private readonly rate: number) {}

  /**
   * 处理传入的数据包,并返回是否允许通过
   */
  processPacket(packetSize: number): boolean {
    // 先漏水
    const currentTime = Date.now();
    const timeElapsed = currentTime - this.lastLeakTime;
    const leakedWater = timeElapsed * this.rate / 1000;  // 计算漏水量
    this.water = Math.max(this.water - leakedWater, 0);  // 漏完水后更新桶内水量
    this.lastLeakTime = currentTime;

    // 加入新的水量
    if (packetSize > this.capacity - this.water) {
      // 数据包大小超过了桶的剩余容量,丢弃该数据包
      return false;
    } else {
      // 数据包可以通过,加入桶中
      this.water += packetSize;
      return true;
    }
  }
}


const bucket = new LeakyBucket(100, 10);  // 创建一个容量为100,流出速率为10的漏桶

function sendDataPacket(packetSize: number): void {
  if (bucket.processPacket(packetSize)) {
    console.log(`发送成功,数据包大小为 ${packetSize} 字节。`);
  } else {
    console.log(`发送失败,数据包大小为 ${packetSize} 字节,超过了桶的容量。`);
  }
}

sendDataPacket(50);   // 发送一个大小为50字节的数据包,应该能够通过
sendDataPacket(80);   // 发送一个大小为80字节的数据包,应该失败
sendDataPacket(30);   // 发送一个大小为30字节的数据包,应该能够通过
sendDataPacket(50);   // 发送一个大小为50字节的数据包,应该失败

当然,我们也可以做一下修改,把流量限制改成次数限制,用来控制请求数量,这就像前端常考的节流功能了。

令牌桶算法

令牌桶算法是另一种经典的限流算法,它的原理是将请求放入一个令牌桶中,然后按照一定速率不断地放出令牌。只有在令牌桶中有令牌时,才能够发出请求。令牌桶算法可以控制单位时间内的请求速率,同时可以应对突发流量,因为只要有足够多的令牌,就可以放请求过去。

class TokenBucket {
  private tokens: number = 0               // 当前桶内令牌的数量
  private lastRefillTime: number =  Date.now();      // 上一次加令牌的时间

  constructor(private readonly capacity: number, private readonly rate: number) {}

  /**
   * 处理传入的请求,并返回是否允许通过
   */
  processRequest(): boolean {
    // 先加令牌
    const currentTime = Date.now();
    const timeElapsed = currentTime - this.lastRefillTime;
    const tokensToAdd = timeElapsed * this.rate / 1000;  // 生成令牌数量
    this.tokens = Math.min(this.tokens + tokensToAdd, this.capacity);  // 加完令牌后更新桶内令牌数量
    this.lastRefillTime = currentTime;

    // 判断是否允许通过
    if (this.tokens < 1) {
      // 限流
      return false;
    } else {
      // 通过
      this.tokens -= 1;
      return true;
    }
  }
}


const bucket = new TokenBucket(10, 1);  

let c = 0
function handleRequest(): void {
  c += 1;
  if (bucket.processRequest()) {
    console.log("通过。",c);
  } else {
    console.log("限流。",c);
  }
}

// 模拟连续请求,每次通过一个
for(let i = 1; i <= 3; i++) {
  setTimeout(() => {
    handleRequest()
    handleRequest()
  }, 1000 * i)
}



总结

漏桶算法:

  • 常用于限制网络流量、控制数据传输速率等场景
  • 类似前端的节流函数,通过恒定速率来控制访问流量
  • 无法应对突发流量

令牌桶算法:

  • 恒定速率发放令牌
  • 可以通过累积令牌来突发流量

关于其他几个算法,且听下回分解


“ 本文正在参加「金石计划」 ”