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

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

之前学习了一个# 令牌桶限流器,但是这个适合单台服务器内部限流,今天又学习到了一个分布式的阻塞式限流器。分布式限流器只要用于多节点需要统一控制访问量的场景。通常需要依赖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 秒)内超过设定上限时,系统通过循环等待(微秒级休眠)方式尝试重试,直到计数器被自动重置(键过期)后才允许继续处理,避免直接拒绝请求。

相关推荐
cwkiller5 分钟前
heapdump深度利用之信息泄露篇
后端
Olrookie2 小时前
若依前后端分离版学习笔记(五)——Spring Boot简介与Spring Security
笔记·后端·学习·spring·ruoyi
小白的代码日记3 小时前
基于 Spring Boot 的小区人脸识别与出入记录管理系统实现
java·spring boot·后端
Chaney不会代码3 小时前
Java7/8中的HashMap深挖
后端
新程快咖员3 小时前
兄弟们,你们安装IDEA 2025.2了吗?java编辑器代码提示失效?临时解决方案新鲜出炉!
后端·intellij idea
调试人生的显微镜3 小时前
移动端网页调试实战,跨设备兼容与触控交互问题排查全流程
后端
onejason4 小时前
《PHP 爬虫实战指南:获取淘宝店铺详情》
前端·后端·php
码事漫谈4 小时前
你的代码可能在偷偷崩溃!
后端
dylan_QAQ4 小时前
【附录】Spring容器的启动过程是怎样的?
后端·spring
白应穷奇4 小时前
编写高性能数据处理代码 02
后端·python