likes
comments
collection
share

Redis场景实战:限流?今天我就自己写了!

作者站长头像
站长
· 阅读数 24

限流系统真是个好东西啊,当你的短信接口被盗刷云厂商的限流系统会帮你一部分流量。当你觉得要损失几千元小钱钱的时候发现,发现这帮人触发基于ip的限流并且被风控系统加入异常名单了基本上除了第一波外没啥损失。哈哈哈,今天加鸡腿!

Redis作为一个高性能的内存数据库,因其快速的读写能力和丰富的数据结构,广泛应用于限流系统中。本文将介绍Redis在限流中的应用,探讨限流的重要性及其在实际应用中的场景。

限流的基本概念

限流的定义

限流是指在一定时间窗口内限制请求的数量,以防止系统过载。常见的限流策略包括:

  • 固定窗口限流:在固定的时间窗口内限制请求数量。
  • 滑动窗口限流:在滑动的时间窗口内限制请求数量。
  • 令牌桶限流:通过令牌桶算法控制请求速率。
  • 漏桶限流:通过漏桶算法平滑请求速率。

限流的应用场景

限流在以下场景中尤为重要:

  • API请求限流:防止API被滥用,保护后台服务。
  • 登录尝试限流:防止暴力破解密码。
  • 秒杀活动限流:防止高并发抢购导致系统崩溃。

Redis限流实现

下面我们将介绍如何使用Redis实现几种常见的限流算法。

细粒度限流器

Redis场景实战:限流?今天我就自己写了!

这个是瞎编的,常见的没有这个。有一个次给小伙伴们分享限流算法的时候忘了漏桶了,然后硬凑了一个。为什么放第一个?因为这个印象深刻,分享过后还因为这个心怦怦跳呢

细粒度限流器允许在不同的时间窗口内设置不同的限流规则。它可以根据不同的时间窗口(如秒、分钟、小时)进行精细化限流控制。例如,可以设置每秒最多允许10次请求,每分钟最多允许100次请求。该算法通过在不同时间窗口内分别记录请求数量来实现精细化控制。

Redis关键操作

  • LPUSH:将一个或多个值插入到列表的头部。用于记录每次请求的时间戳。
  • LRANGE:获取列表中指定范围的元素。用于获取特定时间窗口内的请求记录。
  • LLEN:获取列表的长度。用于判断当前时间窗口内的请求数量。
  • LTRIM:对一个列表进行修剪,只保留指定区间内的元素。用于移除过期的请求记录。
import { convertUnitToSeconds } from "../tools/time";
import { CacheList } from "../TSRedisCacheKit/List";
import { LimiterPassDict, LimitItemConfig, RedisConfig } from "../type";

// 细粒度限流器类
export class FineGrainedRateLimiter extends CacheList {
  limitOption: LimitItemConfig[];
  maxListLength: number;

  /**
   * 创建一个细粒度限流器实例
   * @param redisConfig Redis 配置
   * @param limitOption 限流项的配置数组
   */
  constructor(redisConfig: RedisConfig, limitOption: LimitItemConfig[]) {
    super(redisConfig.prefix, redisConfig.option, redisConfig.redisClient);
    this.limitOption = limitOption;
    this.limitOption.sort((a, b) => b.limit - a.limit);
    this.maxListLength = this.limitOption[0].limit + 20;
  }

  async add(key: string, timestamp: string = Date.now().toString()): Promise<void> {
    await this.push(key, timestamp);
  }

  /**
   * 判断指定键是否受到限流
   * @param key 要进行限流检查的键
   * @returns 如果未受限流,则返回 { status: true };如果受限流,则返回 { status: false, msg: 错误消息 }
   */
  async isRateLimited(key: string): Promise<{ status: boolean; msg?: string }> {
    await this.handleListLength(key);
    const length = await this.length(key);
    const max = this.limitOption[0]?.limit;
    const list = await this.range(length - max, length, key);
    list.reverse();

    // 创建通过时间单位到限制数目和计数的字典
    const passDict: LimiterPassDict = {};
    this.limitOption.forEach((i) => {
      passDict[convertUnitToSeconds(i.unit)] = {
        ...i,
        count: 0,
      };
    });

    return this.baggingCount(list, passDict);
  }

  /**
   * 对列表进行限流计数
   * @param list 时间戳列表,用于限流计数
   * @param passDict 通过时间单位到限制数目和计数的字典
   * @returns 如果未受限流,则返回 { status: true };如果受限流,则返回 { status: false, msg: 错误消息 }
   */
  baggingCount(list: string[], passDict: LimiterPassDict) {
    for (const i in list) {
      const now = new Date().getTime();
      const differ = (now - Number(list[i])) / 1000;
      // 遍历通过时间单位字典,检查是否超过限制
      for (const unit in passDict) {
        if (Number(unit) > differ) {
          passDict[unit].count++;
          if (passDict[unit].count >= passDict[unit].limit) {
            const msg = `触发${passDict[unit].name} ${unit} ${passDict[unit].limit} last`;
            console.log(new Date(), msg, new Date(Number(list[i])));
            return {
              status: true,
              msg: `${msg} ${new Date(Number(list[i]))}`,
            };
          }
        }
      }
    }
    return {
      status: false,
    };
  }

  /**
   * 处理列表长度,如果列表长度超过最大限制,则删除多余的元素
   * @param key 列表的键
   */
  async handleListLength(key: string): Promise<void> {
    const length = await this.length(key);
    if (length > this.maxListLength) {
      const deleteCount = length - this.maxListLength;
      await this.redis.ltrim(this.createKey(key), deleteCount, -1);
      console.log(
        `handleListLength key: ${key} length: ${deleteCount} current: ${
          length - deleteCount
        }`
      );
    }
  }
}

固定窗口限流器

Redis场景实战:限流?今天我就自己写了!

固定窗口限流器在固定的时间窗口内限制请求数量,窗口结束时重置计数。例如,每秒最多允许10次请求。固定窗口限流器适用于简单的限流场景,例如API请求限流、登录尝试限流等。该算法通过在每个固定时间窗口内计数请求数量来实现限流。

Redis关键操作

  • INCR:将键的整数值加1。用于增加当前时间窗口内的请求计数。
  • EXPIRE:设置键的过期时间。用于设置时间窗口的过期时间,以便在窗口结束时重置计数。
import { CacheString } from "../TSRedisCacheKit/String";
import { RedisConfig } from "../type";

/**
 * 固定窗口限流器
 */
export class FixedWindowRateLimiter extends CacheString {
  windowSize: number; // 窗口大小,单位为秒
  limit: number; // 限制值,窗口内允许通过的最大请求数量

  /**
   * 创建一个固定窗口限流器实例
   * @param redisConfig Redis 配置
   * @param windowSize 窗口大小,默认为 10 秒
   * @param limit 限制值,默认为 10
   */
  constructor(
    redisConfig: RedisConfig,
    windowSize: number = 10,
    limit: number = 10
  ) {
    super(redisConfig.prefix, redisConfig.option, redisConfig.redisClient);
    this.windowSize = windowSize;
    this.limit = limit;
  }

  /**
   * 检查给定键的请求是否受到限流
   * @param key 键
   * @param limit 可选的限制值,用于覆盖实例的默认限制值
   * @returns 返回一个包含状态信息的 Promise 对象
   */
  async isRateLimited(
    key: string,
    limit?: number
  ): Promise<{ status: boolean }> {
    const currentCount = await this.redis.incr(this.createKey(key));
    if (currentCount === 1) {
      // 当前窗口开始,设置过期时间
      await this.redis.expire(this.createKey(key), this.windowSize);
    }
    const size = limit || this.limit;
    if (currentCount > size) {
      // 超过限制
      return {
        status: true,
      };
    }
    return {
      status: false,
    };
  }
}

漏桶限流器

Redis场景实战:限流?今天我就自己写了!

漏桶限流器通过漏桶算法平滑请求速率。请求以恒定速率处理,即使突发请求也能平滑处理,防止瞬时过载。例如,每秒处理10个请求,突发请求会被平滑处理。漏桶限流器适用于需要平滑处理请求的场景,例如视频上传、下载服务等。该算法通过模拟漏桶的漏水过程来控制请求速率。

Redis关键操作

  • HGETALL:获取哈希表中所有字段和值。用于获取漏桶的当前状态,包括剩余令牌数和上次漏水时间。
  • HMSET:设置哈希表的多个字段。用于更新漏桶的状态。
  • SET:设置键的值。用于初始化漏桶状态。
import { CacheString } from "../TSRedisCacheKit/String";
import { RedisConfig } from "../type";

export class LeakyBucketRateLimiter extends CacheString {
  capacity: number;
  leakRate: number;

  constructor(redisConfig: RedisConfig, capacity: number = 100, leakRate: number = 10) {
    super(redisConfig.prefix, redisConfig.option, redisConfig.redisClient);
    this.capacity = capacity;
    this.leakRate = leakRate;
  }

  async getBucketState(key: string): Promise<{ tokens: number, lastLeakTime: number }> {
    const state = await this.redis.hgetall(this.createKey(key));
    return {
      tokens: state.tokens ? parseInt(state.tokens) : 0,
      lastLeakTime: state.lastLeakTime ? parseInt(state.lastLeakTime) : Date.now()
    };
  }

  async setBucketState(key: string, tokens: number, lastLeakTime: number): Promise<void> {
    await this.redis.hmset(this.createKey(key), { tokens: tokens.toString(), lastLeakTime: lastLeakTime.toString() });
  }

  async leakTokens(key: string): Promise<void> {
    const state = await this.getBucketState(key);
    const currentTime = Date.now();
    const timeElapsed = (currentTime - state.lastLeakTime) / 1000;
    const leakedTokens = Math.floor(timeElapsed * this.leakRate);
    const newTokens = Math.max(0, state.tokens - leakedTokens);
    await this.setBucketState(key, newTokens, currentTime);
  }

  async isRateLimited(key: string): Promise<{ status: boolean }> {
    await this.leakTokens(key);
    const state = await this.getBucketState(key);
    if (state.tokens < this.capacity) {
      await this.setBucketState(key, state.tokens + 1, state.lastLeakTime);
      return { status: false };
    } else {
      return { status: true };
    }
  }
}

滑动窗口限流器

Redis场景实战:限流?今天我就自己写了!

滑动窗口限流器在滑动的时间窗口内限制请求数量,窗口随时间滑动,实时更新。例如,每秒最多允许10次请求,窗口随时间滑动。滑动窗口限流器适用于需要更精确限流的场景,例如实时数据分析、秒杀活动等。该算法通过在时间窗口内实时更新请求计数来实现限流。

Redis关键操作

  • ZADD:将一个或多个成员添加到有序集合中,或者更新已存在成员的分数。用于记录请求的时间戳。
  • ZRANGE:获取有序集合中指定范围的成员。用于获取特定时间窗口内的请求记录。
  • ZREM:移除有序集合中的一个或多个成员。用于移除过期的请求记录。
  • ZCOUNT:计算有序集合中指定分数范围的成员数量。用于判断当前时间窗口内的请求数量。
import { CacheSortedSet } from "../TSRedisCacheKit/SortedSet";
import { RedisConfig } from "../type";

/**
 * 滑动窗口限流器
 */
export class SlidingWindowRateLimiter extends CacheSortedSet {
  windowSize: number; // 窗口大小,单位为秒
  limit: number; // 限制值,窗口内允许通过的最大请求数量
  initialTime: number;
  sortedSetLength: number;

  /**
   * 创建一个滑动窗口限流器实例
   * @param redisConfig Redis 配置
   * @param windowSize 窗口大小,默认为 10 秒
   * @param limit 限制值,默认为 10
   * @param initialTime 分数差,默认为 1600000000000
   */
  constructor(
    redisConfig: RedisConfig,
    windowSize: number = 10,
    limit: number = 10,
    initialTime = 1690000000000,
    sortedSetLength: number = 60
  ) {
    super(redisConfig.prefix, redisConfig.option, redisConfig.redisClient);
    this.windowSize = windowSize;
    this.limit = limit;
    this.initialTime = initialTime;
    this.sortedSetLength = sortedSetLength;
  }

  async handleSortedSetLength(key: string, currentTime: number) {
    const minTime =
      currentTime -
      1000 * (this.windowSize + this.sortedSetLength) -
      this.initialTime;
    // 删除比窗口早sortedSetLength的时间的数据
    const result = await this.redis.zremrangebyscore(
      this.createKey(key),
      0,
      minTime
    );
    if (result) {
      console.log(`Sorted set length ${result}`, minTime);
    }
  }

  async push(key: string, value: number = Date.now()) {
    return await this.add(value - this.initialTime, value, key);
  }

  /**
   * 检查给定键的请求是否受到限流
   * @param key 键
   * @param limit 可选的限制值,用于覆盖实例的默认限制值
   * @returns 返回一个包含状态信息的 Promise 对象
   */
  async isRateLimited(
    key: string,
    limit?: number
  ): Promise<{
    status: boolean;
    msg?: string;
    limit?: number;
    current?: number;
  }> {
    const currentTime = Date.now(); // 当前时间,单位为秒
    await this.handleSortedSetLength(key, currentTime);
    const count = await this.redis.zcount(
      this.createKey(key),
      currentTime - 1000 * 60 * this.windowSize - this.initialTime,
      currentTime - this.initialTime
    );
    const size = limit || this.limit;
    if (count > size) {
      // 超过限制
      return {
        status: true,
        msg: `Limit exceeded unit ${this.windowSize} limit ${size} currentSerial ${count}`,
      };
    }
    return {
      status: false,
      limit: size,
      current: count,
    };
  }
}

令牌桶限流器

Redis场景实战:限流?今天我就自己写了!

令牌桶限流器通过令牌桶算法控制请求速率,令牌以固定速率添加,请求需要消耗令牌。例如,每秒最多允许10次请求,但允许短时间内的突发流量。令牌桶限流器适用于需要控制请求速率且允许一定程度突发流量的场景,例如API网关限流、流媒体服务等。该算法通过在固定速率下向桶中添加令牌,并在请求到来时消耗令牌来控制请求速率。

Redis关键操作

  • GET:获取键的值。用于获取当前桶中剩余的令牌数。
  • SET:设置键的值。用于更新桶中剩余的令牌数。
  • DECRBY:将键的整数值减去指定值。用于消耗令牌。
  • INCRBY:将键的整数值加上指定值。用于添加令牌。
import { CacheString } from "../TSRedisCacheKit/String";
import { RedisConfig } from "../type";

export class TokenBucketRateLimiter extends CacheString {
  maxTokens: number;
  refillRate: number;

  constructor(
    redisConfig: RedisConfig,
    maxTokens: number = 100,
    refillRate: number = 10
  ) {
    super(redisConfig.prefix, redisConfig.option, redisConfig.redisClient);
    this.maxTokens = maxTokens;
    this.refillRate = refillRate;
  }

  async getTokens(key: string): Promise<number> {
    const tokens = await this.redis.get(this.createKey(key));
    return tokens ? parseInt(tokens) : this.maxTokens;
  }

  async setTokens(key: string, tokens: number): Promise<void> {
    await this.redis.set(this.createKey(key), tokens.toString());
  }

  async refillTokens(key: string, lastRefillTime: number): Promise<void> {
    const currentTime = Date.now();
    const timeElapsed = (currentTime - lastRefillTime) / 1000;
    const newTokens = Math.min(
      this.maxTokens,
      Math.floor(timeElapsed * this.refillRate)
    );
    await this.setTokens(key, newTokens);
  }

  async isRateLimited(key: string): Promise<{ status: boolean }> {
    const tokens = await this.getTokens(key);
    if (tokens > 0) {
      await this.setTokens(key, tokens - 1);
      return { status: false };
    } else {
      return { status: true };
    }
  }
}

限流是保障系统稳定性和可用性的重要手段。通过Redis的高性能和丰富的数据结构,可以实现多种限流算法,包括细粒度限流、固定窗口限流、漏桶限流、滑动窗口限流和令牌桶限流。根据具体的应用场景选择合适的限流策略,可以有效地防止系统过载,保障服务的稳定运行。

适用场景

限流器类型算法解释适用场景性能具体场景枚举
细粒度限流器 (FineGrainedRateLimiter)根据不同时间窗口进行精细化限流控制,可以在多个时间窗口内分别设置限流规则。复杂的API限流场景,需要对不同时间窗口进行精细化限流控制;例如,某API每秒最多允许10次请求,每分钟最多允许100次请求。性能较高,但需要对多个时间窗口进行计数和判断,适用于需要精细化控制的场景。API请求限流、用户操作限流、复杂业务场景限流、支付请求限流、购物车操作限流、评论发布限流。
固定窗口限流器 (FixedWindowRateLimiter)在固定的时间窗口内限制请求数量,窗口结束时重置计数。简单的API限流场景,例如每秒最多允许10次请求;登录尝试限流,每分钟最多允许5次登录尝试。性能较高,算法简单,适用于大多数简单限流场景。API请求限流、登录尝试限流、短信验证码发送限流、注册请求限流、投票限流、点赞限流。
漏桶限流器 (LeakyBucketRateLimiter)请求以恒定速率处理,即使突发请求也能平滑处理,防止瞬时过载。需要平滑请求速率的场景,例如视频上传、下载服务;流量控制,防止瞬时流量过大导致系统崩溃。性能较高,适用于需要平滑处理请求的场景。视频上传、下载限流、消息队列限流、流量控制、文件传输限流、API网关限流。
滑动窗口限流器 (SlidingWindowRateLimiter)在滑动的时间窗口内限制请求数量,窗口随时间滑动,实时更新。需要更精确限流的场景,例如实时数据分析,防止突发高峰流量;秒杀活动限流,防止高并发抢购导致系统崩溃。性能较高,但需要实时更新窗口,适用于需要精确控制的场景。实时数据分析限流、秒杀活动限流、实时监控限流、金融交易限流、股票交易限流、实时消息处理限流。
令牌桶限流器 (TokenBucketRateLimiter)通过令牌桶算法控制请求速率,令牌以固定速率添加,请求需要消耗令牌。需要控制请求速率且允许一定程度突发流量的场景,例如API网关限流,每秒最多允许10次请求,但允许短时间内的突发流量。性能较高,适用于需要控制请求速率且允许突发流量的场景。API网关限流、流媒体服务限流、文件上传限流、游戏服务器限流、实时聊天限流、直播弹幕限流。

详细解释

  1. 细粒度限流器 (FineGrainedRateLimiter)

    • 算法解释:这种限流器允许在不同的时间窗口内设置不同的限流规则。例如,可以设置每秒最多10次请求,每分钟最多100次请求。
    • 适用场景:适用于复杂的API限流场景,需要对不同时间窗口进行精细化限流控制。例如,某API每秒最多允许10次请求,每分钟最多允许100次请求。
    • 性能:性能较高,但需要对多个时间窗口进行计数和判断,适用于需要精细化控制的场景。
    • 具体场景枚举:API请求限流、用户操作限流、复杂业务场景限流、支付请求限流、购物车操作限流、评论发布限流。
  2. 固定窗口限流器 (FixedWindowRateLimiter)

    • 算法解释:在固定的时间窗口内限制请求数量,窗口结束时重置计数。例如,每秒最多允许10次请求。
    • 适用场景:适用于简单的API限流场景,例如每秒最多允许10次请求;登录尝试限流,每分钟最多允许5次登录尝试。
    • 性能:性能较高,算法简单,适用于大多数简单限流场景。
    • 具体场景枚举:API请求限流、登录尝试限流、短信验证码发送限流、注册请求限流、投票限流、点赞限流。
  3. 漏桶限流器 (LeakyBucketRateLimiter)

    • 算法解释:请求以恒定速率处理,即使突发请求也能平滑处理,防止瞬时过载。例如,每秒处理10个请求,突发请求会被平滑处理。
    • 适用场景:适用于需要平滑请求速率的场景,例如视频上传、下载服务;流量控制,防止瞬时流量过大导致系统崩溃。
    • 性能:性能较高,适用于需要平滑处理请求的场景。
    • 具体场景枚举:视频上传、下载限流、消息队列限流、流量控制、文件传输限流、API网关限流。
  4. 滑动窗口限流器 (SlidingWindowRateLimiter)

    • 算法解释:在滑动的时间窗口内限制请求数量,窗口随时间滑动,实时更新。例如,每秒最多允许10次请求,窗口随时间滑动。
    • 适用场景:适用于需要更精确限流的场景,例如实时数据分析,防止突发高峰流量;秒杀活动限流,防止高并发抢购导致系统崩溃。
    • 性能:性能较高,但需要实时更新窗口,适用于需要精确控制的场景。
    • 具体场景枚举:实时数据分析限流、秒杀活动限流、实时监控限流、金融交易限流、股票交易限流、实时消息处理限流。
  5. 令牌桶限流器 (TokenBucketRateLimiter)

    • 算法解释:通过令牌桶算法控制请求速率,令牌以固定速率添加,请求需要消耗令牌。例如,每秒最多允许10次请求,但允许短时间内的突发流量。
    • 适用场景:适用于需要控制请求速率且允许一定程度突发流量的场景,例如API网关限流,每秒最多允许10次请求,但允许短时间内的突发流量。
    • 性能:性能较高,适用于需要控制请求速率且允许突发流量的场景。
    • 具体场景枚举:API网关限流、流媒体服务限流、文件上传限流、游戏服务器限流、实时聊天限流、直播弹幕限流。

常见场景代码示例

1. API请求限流

API请求限流是一个非常经典的场景,通常用于防止API被滥用,保护后台服务。我们可以使用固定窗口限流器来实现这个场景。

示例代码(固定窗口限流器)

import { FixedWindowRateLimiter } from './FixedWindowRateLimiter'; // FixedWindowRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new FixedWindowRateLimiter({
  prefix: 'api',
  option: {},
  redisClient: redisClient,
}, 60, 100); // 每分钟最多允许100次请求

async function handleApiRequest(userId: string) {
  const key = `user:${userId}:api_requests`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Rate limit exceeded' }; // 返回429状态码表示限流
  }
  // 处理API请求的逻辑
  return { status: 200, message: 'Request successful' };
}

2. 登录尝试限流

登录尝试限流用于防止暴力破解密码,可以使用滑动窗口限流器来实现。

示例代码(滑动窗口限流器)

import { SlidingWindowRateLimiter } from './SlidingWindowRateLimiter'; // SlidingWindowRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new SlidingWindowRateLimiter({
  prefix: 'login',
  option: {},
  redisClient: redisClient,
}, 60, 5); // 每分钟最多允许5次登录尝试

async function handleLoginAttempt(userId: string) {
  const key = `user:${userId}:login_attempts`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Too many login attempts, please try again later' }; // 返回429状态码表示限流
  }
  // 处理登录逻辑
  return { status: 200, message: 'Login successful' };
}

3. 秒杀活动限流

秒杀活动限流用于防止高并发抢购导致系统崩溃,可以使用滑动窗口限流器来实现。

示例代码(滑动窗口限流器)

import { SlidingWindowRateLimiter } from './SlidingWindowRateLimiter'; // SlidingWindowRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new SlidingWindowRateLimiter({
  prefix: 'seckill',
  option: {},
  redisClient: redisClient,
}, 1, 1000); // 每秒最多允许1000次请求

async function handleSeckillRequest(userId: string, productId: string) {
  const key = `product:${productId}:seckill_requests`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Too many requests, please try again later' }; // 返回429状态码表示限流
  }
  // 处理秒杀请求的逻辑
  return { status: 200, message: 'Seckill request successful' };
}

4. 视频上传限流

视频上传限流用于平滑处理请求速率,可以使用漏桶限流器来实现。

示例代码(漏桶限流器)

import { LeakyBucketRateLimiter } from './LeakyBucketRateLimiter'; // LeakyBucketRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new LeakyBucketRateLimiter({
  prefix: 'upload',
  option: {},
  redisClient: redisClient,
}, 100, 10); // 容量100,漏速10个/秒

async function handleVideoUpload(userId: string) {
  const key = `user:${userId}:video_uploads`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Upload rate limit exceeded' }; // 返回429状态码表示限流
  }
  // 处理视频上传的逻辑
  return { status: 200, message: 'Upload successful' };
}

5. 实时聊天限流

实时聊天限流用于控制消息发送速率,可以使用令牌桶限流器来实现。

示例代码(令牌桶限流器)

import { TokenBucketRateLimiter } from './TokenBucketRateLimiter'; // TokenBucketRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new TokenBucketRateLimiter({
  prefix: 'chat',
  option: {},
  redisClient: redisClient,
}, 100, 10); // 最大令牌数100,添加速率10个/秒

async function handleChatMessage(userId: string) {
  const key = `user:${userId}:chat_messages`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Too many messages, please try again later' }; // 返回429状态码表示限流
  }
  // 处理聊天消息的逻辑
  return { status: 200, message: 'Message sent' };
}

6. 短信验证码发送限流

短信验证码发送限流用于防止用户频繁请求验证码,可以使用细粒度限流器来实现。这里展示了如何使用 FineGrainedRateLimiter 实现每天最多发送10条短信,每小时最多发送5条短信,每分钟最多发送1条短信的限流策略。

示例代码(细粒度限流器)

import { FineGrainedRateLimiter } from './FineGrainedRateLimiter'; // FineGrainedRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

// Redis 配置
const redisClient = createClient();
const redisConfig = {
  prefix: 'sms',
  option: {},
  redisClient: redisClient,
};

// 限流配置
const limitOption = [
  { unit: 'day', limit: 10, name: 'Daily Limit' },
  { unit: 'hour', limit: 5, name: 'Hourly Limit' },
  { unit: 'minute', limit: 1, name: 'Minutely Limit' },
];

// 创建限流器实例
const rateLimiter = new FineGrainedRateLimiter(redisConfig, limitOption);

async function handleSmsRequest(userId: string) {
  const key = `user:${userId}:sms_requests`;
  const isLimited = await rateLimiter.isRateLimited(key);

  if (isLimited.status) {
    return { status: 429, message: isLimited.msg || 'Too many requests, please try again later' }; // 返回429状态码表示限流
  }

  // 记录请求时间戳
  await rateLimiter.add(key);

  // 处理短信验证码发送的逻辑
  return { status: 200, message: 'SMS sent successfully' };
}

// 示例调用
(async () => {
  const userId = 'user123';

  // 模拟多次请求
  for (let i = 0; i < 12; i++) {
    const response = await handleSmsRequest(userId);
    console.log(`Request ${i + 1}:`, response);
    await new Promise((resolve) => setTimeout(resolve, 1000)); // 每秒钟发送一次请求
  }
})();

7. 注册请求限流

注册请求限流用于防止用户频繁注册,可以使用滑动窗口限流器来实现。

示例代码(滑动窗口限流器)

import { SlidingWindowRateLimiter } from './SlidingWindowRateLimiter'; // SlidingWindowRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new SlidingWindowRateLimiter({
  prefix: 'register',
  option: {},
  redisClient: redisClient,
}, 3600, 3); // 每小时最多允许3次请求

async function handleRegisterRequest(ipAddress: string) {
  const key = `ip:${ipAddress}:register_requests`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Too many registration attempts, please try again later' }; // 返回429状态码表示限流
  }
  // 处理注册请求的逻辑
  return { status: 200, message: 'Registration successful' };
}

8. 流媒体服务限流

流媒体服务限流用于控制流媒体请求速率,可以使用令牌桶限流器来实现。

示例代码(令牌桶限流器)

import { TokenBucketRateLimiter } from './TokenBucketRateLimiter'; // TokenBucketRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new TokenBucketRateLimiter({
  prefix: 'streaming',
  option: {},
  redisClient: redisClient,
}, 100, 10); // 最大令牌数100,添加速率10个/秒

async function handleStreamingRequest(userId: string) {
  const key = `user:${userId}:streaming_requests`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Too many requests, please try again later' }; // 返回429状态码表示限流
  }
  // 处理流媒体请求的逻辑
  return { status: 200, message: 'Streaming started' };
}

9. 实时消息处理限流

实时消息处理限流用于控制消息处理速率,可以使用滑动窗口限流器来实现。

示例代码(滑动窗口限流器)

import { SlidingWindowRateLimiter } from './SlidingWindowRateLimiter'; // SlidingWindowRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new SlidingWindowRateLimiter({
  prefix: 'message',
  option: {},
  redisClient: redisClient,
}, 60, 100); // 每分钟最多允许100次请求

async function handleMessageProcessing(userId: string) {
  const key = `user:${userId}:message_requests`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Too many requests, please try again later' }; // 返回429状态码表示限流
  }
  // 处理消息的逻辑
  return { status: 200, message: 'Message processed' };
}

10. 直播弹幕限流

直播弹幕限流用于控制弹幕发送速率,可以使用令牌桶限流器来实现。

示例代码(令牌桶限流器)

import { TokenBucketRateLimiter } from './TokenBucketRateLimiter'; // TokenBucketRateLimiter
import { createClient } from '../dao/redis';  //  Redis实例化

const redisClient = createClient();
const rateLimiter = new TokenBucketRateLimiter({
  prefix: 'danmu',
  option: {},
  redisClient: redisClient,
}, 50, 5); // 最大令牌数50,添加速率5个/秒

async function handleDanmuRequest(userId: string) {
  const key = `user:${userId}:danmu_requests`;
  const isLimited = await rateLimiter.isRateLimited(key);
  if (isLimited.status) {
    return { status: 429, message: 'Too many requests, please try again later' }; // 返回429状态码表示限流
  }
  // 处理弹幕发送的逻辑
  return { status: 200, message: 'Danmu sent' };
}

总结

本文详细介绍了在不同应用场景中使用限流(Rate Limiting)技术来保护系统稳定性和防止滥用的实践。通过具体的代码示例,展示了如何在各种常见的场景中实现限流策略,确保系统在高并发和恶意请求情况下依然能够平稳运行。

转载自:https://juejin.cn/post/7395093207565402150
评论
请登录