分布式阻塞式限流学习及分享

分享一个在公司代码学习到的分布式阻塞式限流器

之前学习了一个# 令牌桶限流器,但是这个适合单台服务器内部限流,今天又学习到了一个分布式的阻塞式限流器。分布式限流器只要用于多节点需要统一控制访问量的场景。通常需要依赖Redis或者ZK这种中间件来实现。

话不多说,直接贴代码,看实现。

1.分布式限流器的方法定义

java 复制代码
/**
 * 分布式阻塞式限流.
 *
 * @param key the key
 * @param period the period,时间单位:秒
 * @param count the count
 * @param microseconds the microseconds of block
 * @return the boolean
 */
boolean blockLimit(final String key, final int period, final int count, final int microseconds);
参数 含义 举例
key 用来区分限流对象的唯一标识,可以是接口名、用户ID、IP等 "user:123:sendSms" 表示用户123发送短信的限流
period 限流的时间窗口(单位:秒) ,在这个时间段内做限流控制 10 表示 10 秒内最多只能访问一定次数
count 时间窗口内允许的最大访问次数 5 表示 10 秒内最多允许访问 5 次
microseconds 超过限流阈值后,最多可以等待多久(单位:微秒) ,如果等待不到令牌,就返回失败 2_000_000 表示最多等 2 秒

2.分布式限流器方法的实现

arduino 复制代码
/** 阻塞式限流 */
@Override
public boolean blockLimit(
    final String key, final int period, final int count, final int microseconds) {
  if (isNotFull(key, period, count)) {
    return true;
  } else {
    int times = 0;
    // 自旋锁阻塞
    while (times * 100 < microseconds) {
      times++;
      try {
        TimeUnit.MILLISECONDS.sleep(100);
        if (isNotFull(key, period, count)) {
          logger.info("自旋{}ms后获取锁", times * 100);
          return true;
        }
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
  }
  logger.error("项目{}阻塞{}ms后,仍然没有获得锁,不再等待锁,继续执行", key, microseconds);
  return false;
}

解释:在并发环境下,如果请求超过了设定的阈值(即:在指定时间内请求次数过多),则阻塞等待一段时间(通过自旋轮询判断是否可以放行) ,如果超时仍未获得资格,就返回 false,代表限流失败。

3.接着贴出isNotFull的实现:

vbnet 复制代码
@Override
public boolean isNotFull(String key, int period, int count) {
  ImmutableList<String> keys = ImmutableList.of(StringUtils.join("limit", ":", key));
  String luaScript = buildLuaScript();
  RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
  Long currentCount = stringRedisTemplate.execute(redisScript, keys, count, period);
  return Objects.nonNull(currentCount) && currentCount <= count;
}

解释:在指定的时间窗口 period 内,判断某个操作(用 key 标识)是否已经超过了允许的请求次数 count。 如果没有超限,返回 true,可以执行;如果已达上限,返回 false,触发限流

这里需要贴出lua脚本的代码。

lua:

swift 复制代码
private @NotNull String buildLuaScript() {
  return "local c"
      + "\nc = redis.call('get',KEYS[1])"
      +
      // 调用不超过最大值,则直接返回
      "\nif c and tonumber(c) > tonumber(ARGV[1]) then"
      + "\nreturn tonumber(c);"
      + "\nend"
      +
      // 执行计算器自加
      "\nc = redis.call('incr',KEYS[1])"
      + "\nif tonumber(c) == 1 then"
      +
      // 从第一次调用开始限流,设置对应键值的过期时间
      "\nredis.call('expire',KEYS[1],ARGV[2])"
      + "\nend"
      + "\nreturn tonumber(c);";
}

解释:在固定的时间窗口 ARGV[2](比如 60 秒)内,判断某个 key(KEYS[1])对应的访问次数是否超过了阈值 ARGV[1](比如 100 次),如果没有,就允许放行并自动计数;否则拒绝。

当达到窗口时间的限流上限后,不会一直阻塞。设置的时间窗口结束后,Redis 中的 key 会自动过期,限流计数器就会重置,新的请求会被放行,这时便会重新自增计数。

最后总结一下这个分布式阻塞式限流器的整体思想: 当一个接口不止一个实例在调用时,我们需要在 多个节点之间统一地控制访问频率 如果超过了限制,不是立刻返回失败,而是等等看行不行(阻塞式的限流)。

总的来说就是通过 Redis + Lua 脚本实现固定时间窗口内的原子计数操作,结合业务代码进行自旋阻塞,确保请求在限流条件下有序接入。当某个 key 的请求次数在指定周期(如 1 秒)内超过设定上限时,系统通过循环等待(微秒级休眠)方式尝试重试,直到计数器被自动重置(键过期)后才允许继续处理,避免直接拒绝请求。

相关推荐
菜鸟小九1 天前
SSM(MybatisPlus)
java·开发语言·spring boot·后端
不爱编程的小九九1 天前
小九源码-springboot051-智能推荐旅游平台
java·spring boot·后端
数据知道1 天前
Go基础:常用数学函数处理(主要是math包rand包的处理)
开发语言·后端·golang·go语言
期待のcode1 天前
MyBatis框架—延迟加载与多级缓存
java·数据库·后端·缓存·mybatis
数据知道1 天前
Go基础:文件与文件夹操作详解
开发语言·后端·golang·go语言
华仔啊1 天前
Spring 配置混乱?搞懂这两个核心组件,问题真能少一半
java·后端·spring
喂完待续1 天前
【序列晋升】45 Spring Data Elasticsearch 实战:3 个核心方案破解索引管理与复杂查询痛点,告别低效开发
java·后端·spring·big data·spring data·序列晋升
forever銳1 天前
java中如何保证接口幂等性
java·后端
IT_陈寒1 天前
告别低效!用这5个Python技巧让你的数据处理速度提升300% 🚀
前端·人工智能·后端
程序员NEO1 天前
B站油管抖音一键笔记
后端