从Redis转向Cloudflare Durable Objects:构建高性能边缘限流器

本文详细介绍了如何用Cloudflare Durable Objects替代Redis构建高性能边缘限流器,实现5ms响应时间、70%成本节省,并在500+请求/秒的流量下保持稳定运行。

为什么我放弃Redis转向Cloudflare Durable Objects构建限流器

当我们的AI图像生成器在社交媒体爆红时,传统限流设置无法应对Cloudflare边缘网络的分布式负载。这次经历让我意识到,无服务器环境中的限流需要完全不同的方法。

传统方法的不足

在无服务器环境中,特别是跨多个边缘位置的分布式环境中,Redis面临诸多挑战:

  • 延迟问题:每次限流检查都需要往返Redis实例,增加50-200ms延迟
  • 单点故障:整个限流系统依赖Redis可用性
  • 冷启动问题:无服务器函数在冷启动时需要建立Redis连接
  • 地理复杂性:跨区域运行Redis副本成本高昂且带来数据一致性挑战

Durable Objects的解决方案

Cloudflare Durable Objects通过提供直接在边缘运行的有状态计算原语,优雅地解决了这些问题。每个Durable Object实例维护自己的持久状态,可以处理并发请求同时确保强一致性。

构建限流器

以下是使用Durable Objects构建生产就绪限流器的完整实现:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
import { DurableObject } from "cloudflare:workers";

interface ThrottleState {
  limitTimes: number;
  limitEndTimeMs: number;
  executedTimesCurrentCycle: number;
  currentCycle: number;
}

export interface TryApplyOptions {
  limitCycleExecutionTimes: number;
  limitCycleTimeMs: number;
}

export interface ThrottlerResponse {
  granted: boolean;
  state: ThrottleState;
}

export class ThrottlerDO extends DurableObject {
  limitCycleExecutionTimes = 10; // 默认:每个周期10个请求
  limitCycleTimeMs = 10 * 60 * 1000; // 默认:10分钟

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
  }

  async getState(): Promise<ThrottlerResponse> {
    let state = await this.ctx.storage.get('throttle_state') as ThrottleState | null;
    
    if (!state) {
      state = {
        limitTimes: 0,
        limitEndTimeMs: 0,
        executedTimesCurrentCycle: 0,
        currentCycle: 0,
      };
    }
    
    const currentMs = Date.now();
    
    // 如果周期已过期,重置状态
    if (state.limitEndTimeMs > 0 && currentMs > state.limitEndTimeMs) {
      state = {
        ...state,
        limitEndTimeMs: 0,
        executedTimesCurrentCycle: 0,
      };
    }
    
    const granted = state.executedTimesCurrentCycle < this.limitCycleExecutionTimes;
    return { granted, state };
  }

  async tryApply(options?: TryApplyOptions): Promise<ThrottlerResponse> {
    if (options) {
      this.limitCycleExecutionTimes = options.limitCycleExecutionTimes;
      this.limitCycleTimeMs = options.limitCycleTimeMs;
    }
    
    let granted = false;
    let state = await this.ctx.storage.get('throttle_state') as ThrottleState | null;
    
    if (!state) {
      state = {
        limitTimes: 0,
        limitEndTimeMs: 0,
        executedTimesCurrentCycle: 0,
        currentCycle: 0,
      };
    }

    const currentMs = Date.now();
    
    // 如果周期过期,重置周期
    if (state.limitEndTimeMs > 0 && currentMs > state.limitEndTimeMs) {
      state.limitEndTimeMs = 0;
      state.executedTimesCurrentCycle = 0;
    }
    
    // 检查请求是否可以被批准
    if (state.executedTimesCurrentCycle < this.limitCycleExecutionTimes) {
      state.executedTimesCurrentCycle++;
      granted = true;
    } else {
      state.limitTimes++;
      granted = false;
    }
    
    // 如果需要,初始化新周期
    if (state.limitEndTimeMs === 0) {
      state.limitEndTimeMs = currentMs + this.limitCycleTimeMs;
      state.currentCycle++;
      
      if (state.currentCycle >= 65535) {
        state.currentCycle = 1;
      }
    }
    
    await this.ctx.storage.put('throttle_state', state);
    return { granted, state };
  }
}

使用限流器

将限流器集成到Worker中很简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export default {
  async fetch(request: Request, env: Env) {
    // 基于用户标识创建DO实例
    const userId = request.headers.get('user-id') || 'anonymous';
    const id = env.THROTTLER.idFromName(userId);
    const throttler = env.THROTTLER.get(id);
    
    // 检查限流
    const result = await throttler.tryApply({
      limitCycleExecutionTimes: 5,
      limitCycleTimeMs: 60 * 1000, // 1分钟
    });
    
    if (!result.granted) {
      return new Response('超出速率限制', { 
        status: 429,
        headers: {
          'Retry-After': '60'
        }
      });
    }
    
    // 处理实际请求
    return processRequest(request);
  }
};

实际性能表现

该限流器在生产环境中运行数月,结果令人印象深刻:

  • 延迟:平均限流检查在5ms内完成,相比之前Redis设置的80-150ms
  • 可靠性:部署以来零限流相关停机时间
  • 成本效益:以之前Redis成本的30%运行,同时处理10倍以上的请求
  • 地理性能:亚太用户与北美用户享受相同的低延迟

关键要点

Durable Objects在以下场景中表现出色:

  • 边缘有状态逻辑
  • 强一致性保证
  • 自动扩展和地理分布
  • 全球应用的低延迟

但在以下场景可能不是最佳选择:

  • 需要跨Cloudflare之外的平台兼容性
  • 需要传统数据库的复杂查询能力
  • 跨多个应用的共享状态
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计