← 中文资源

简体中文版本

BBR 拥塞控制

BBR 通过建模路径的交付速率与往返时延来最大化吞吐并保持短队列,取代会把无线丢包与深缓冲误读为拥塞的基于丢包的算法。

阅读英文原文

概念定义

经典算法把丢包当拥塞并在任何丢包时收缩窗口。无线链路的丢包只是射频噪声;深缓冲路径上,丢包要等队列溢出才出现,此时延迟早已崩塌。BBR 改为测量交付速率与最小 RTT,并按两者乘积控制发送节奏。

工作机制

发送端在交替阶段探测更高带宽与更低 RTT,维护路径的最大化模型。配速(pacing)按目标速率铺开发包,而不是倾泻整个窗口的突发。BBRv2 根据部署经验改进了公平性、丢包响应与 ECN 处理。

使用场景

面向有损或移动接入网络的公共服务能同时获得吞吐与尾延迟收益。QUIC 实现常默认 BBR 变体,因为用户态传输做配速很容易。长寿命批量流获益最大,请求响应型流量差异较小。

常见误区

BBR 并不消除延迟,只是约束队列占用,传播与接入延迟依旧。早期版本在某些路径上会饿死 CUBIC 流,与基于丢包的流共存必须测试。浅缓冲依赖丢包信号过载时,速率型发送者的交互方式不同。评估应把重传、有效吞吐与 p99 延迟放在一起看。

代码示例

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

.animate-fade {
  animation: fadeIn 0.3s ease-out forwards;
}

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}
class CacheNode<K, V> {
  public previous: CacheNode<K, V> | null = null;
  public next: CacheNode<K, V> | null = null;

  public constructor(
    public readonly key: K,
    public value: V,
    public frequency: number,
  ) {}
}

class FrequencyList<K, V> {
  private head: CacheNode<K, V> | null = null;
  private tail: CacheNode<K, V> | null = null;
  public size = 0;

  public addFront(node: CacheNode<K, V>): void {
    node.previous = null;
    node.next = this.head;
    if (this.head !== null) this.head.previous = node;
    this.head = node;
    if (this.tail === null) this.tail = node;
    this.size += 1;
  }

  public remove(node: CacheNode<K, V>): void {
    if (node.previous !== null) node.previous.next = node.next;
    else this.head = node.next;
    if (node.next !== null) node.next.previous = node.previous;
    else this.tail = node.previous;
    node.previous = null;
    node.next = null;
    this.size -= 1;
  }

  public removeLast(): CacheNode<K, V> | null {
    if (this.tail === null) return null;
    const node = this.tail;
    this.remove(node);
    return node;
  }
}

export class LfuCache<K, V> {
  private readonly entries = new Map<K, CacheNode<K, V>>();
  private readonly frequencies = new Map<number, FrequencyList<K, V>>();
  private lowestFrequency = 0;

  public constructor(private readonly capacity: number) {
    if (!Number.isInteger(capacity) || capacity < 1) {
      throw new RangeError("capacity must be a positive integer");
    }
  }

  public get size(): number {
    return this.entries.size;
  }

  public get(key: K): V | undefined {
    const node = this.entries.get(key);
    if (node === undefined) return undefined;
    this.touch(node);
    return node.value;
  }

  public peek(key: K): V | undefined {
    return this.entries.get(key)?.value;
  }

  public put(key: K, value: V): this {
    const existing = this.entries.get(key);
    if (existing !== undefined) {
      existing.value = value;
      this.touch(existing);
      return this;
    }

    if (this.entries.size >= this.capacity) this.evict();
    const node = new CacheNode(key, value, 1);
    this.entries.set(key, node);
    this.listFor(1).addFront(node);
    this.lowestFrequency = 1;
    return this;
  }

  public delete(key: K): boolean {
    const node = this.entries.get(key);
    if (node === undefined) return false;
    this.removeNode(node);
    return true;
  }

  private touch(node: CacheNode<K, V>): void {
    const oldFrequency = node.frequency;
    const oldList = this.frequencies.get(oldFrequency);
    if (oldList === undefined) {
      throw new Error("cache invariant violated");
    }
    oldList.remove(node);
    if (oldList.size === 0) {
      this.frequencies.delete(oldFrequency);
      if (this.lowestFrequency === oldFrequency) {
        this.lowestFrequency = oldFrequency + 1;
      }
    }
    node.frequency = oldFrequency + 1;
    this.listFor(node.frequency).addFront(node);
  }

  private evict(): void {
    const list = this.frequencies.get(this.lowestFrequency);
    if (list === undefined) return;
    const node = list.removeLast();
    if (node === null) return;
    this.entries.delete(node.key);
    if (list.size === 0) this.frequencies.delete(this.lowestFrequency);
  }

  private removeNode(node: CacheNode<K, V>): void {
    const list = this.frequencies.get(node.frequency);
    if (list === undefined) {
      throw new Error("cache invariant violated");
    }
    list.remove(node);
    this.entries.delete(node.key);
    if (list.size === 0) this.frequencies.delete(node.frequency);
  }

  private listFor(frequency: number): FrequencyList<K, V> {
    let list = this.frequencies.get(frequency);
    if (list === undefined) {
      list = new FrequencyList<K, V>();
      this.frequencies.set(frequency, list);
    }
    return list;
  }
}

const cache = new LfuCache<string, number>(2);
cache.put("a", 1).put("b", 2);
console.log(cache.get("a"));
cache.put("c", 3);
console.log(cache.get("b"), cache.peek("a"), cache.get("c"));
cache.put("d", 4);
console.log(cache.get("a"), cache.get("c"), cache.get("d"));
console.log(cache.size);