基于 Redis +Lua+ ZooKeeper 的轻量级内嵌式限流

基于 Redis +Lua+ ZooKeeper 的轻量级内嵌式限流


一、一句话说清楚:我们要做什么?

用 Redis + Lua + ZooKeeper,在不改业务代码的前提下,给你的接口加上"智能红绿灯"------该放行时秒过,该拦截时果断拒绝。

本方案基于 Spring Boot Starter 封装,通过 AOP/Filter 拦截请求,结合 Redis + Lua实现高性能分布式限流,ZooKeeper 动态管理规则,真正做到:

  • 零侵入:业务代码加个注解就行;
  • 实时生效:规则改完立刻生效,不用重启;
  • 高可用兜底:Redis 或 ZK 挂了也不影响主业务;
  • 细粒度控制:支持全局、接口、客户端等多维度限流。
  • 分布式限流: 基于 Redis 的 INCR + EXPIRE+ 原子操作(Lua) 实现分布式限流(秒级并发、自定义时间窗口)。

二、限流流程图

✅ 所有核心逻辑都在应用进程内完成,无额外网关层,低延迟、低运维成本。


📊 流程图说明:请求限流处理流程

本方案中一次 HTTP 请求从进入系统到最终执行业务逻辑的完整限流处理流程,体现了 "拦截 → 判断 → 决策 → 执行" 的核心链路。

  1. HTTP 请求到达

    • 客户端发起 HTTP 请求,进入服务端。
    • 请求首先进入统一入口(如 Servlet Filter 或 Spring AOP 切面)。
  2. Filter / AOP 拦截器触发

    • 系统通过 Filter(全局 HTTP 层)或 @RateLimited 注解驱动的 AOP(方法级)进行拦截。
    • 根据请求路径、客户端 IP、用户 ID 等信息,确定是否需要限流。
  3. 读取内存中的限流规则

    • 从本地 JVM 内存中获取当前生效的限流规则(如 QPS、时间窗口等)。
    • 规则由 ZooKeeper 动态推送并实时更新,应用通过 Curator 监听节点变化,确保配置变更秒级生效。
  4. 调用 Redis 执行限流判断

    • 使用 Redis 的 INCR + EXPIRE 原子操作(通过 Lua 脚本保证一致性),对当前请求进行计数与判断。
    • 若请求数未超阈值 → 放行;否则 → 拒绝。
  5. 决策执行 + 日志记录

    • 放行:继续执行后续业务逻辑;
    • 拒绝:返回 429(Too Many Requests)状态码,并记录结构化日志(含时间、IP、接口、结果等);
    • 日志异步写入(如 RocketMQ),避免阻塞主线程。
  6. 正常执行业务逻辑

    • 若通过限流检查,请求进入业务方法,完成正常处理流程。

⚙️ 关键设计亮点

步骤 设计优势
ZooKeeper 动态推送规则 配置无需重启,支持灰度发布和快速调整
内存缓存规则 即使 ZK 宕机,仍可用最后有效规则,保障高可用
Redis 原子操作 分布式环境下精准计数,无并发问题
日志异步记录 不影响主流程性能,便于监控分析

✅ 总结一句话:

一个请求,三步走:拦截 → 查规则 → 判限流,全程零侵入、实时生效、高可用。

三、技术架构图

四、双层限流架构图

本图展示了系统采用的双层流量控制机制

  1. 全局级流控(Filter 层) :所有 HTTP 请求首先进入 Filter,基于全局规则(如总 QPS)进行初步限流判断,使用 Redis 原子操作实现分布式计数;
  2. 接口级流控(Service 层) :若未被全局拦截,则进入业务方法,通过 @RateLimited 注解解析接口级规则,进行更细粒度的限流控制;
  3. 限流规则由 ZooKeeper 动态推送并实时生效,Redis 保证高并发下的计数一致性;
  4. 任一层触发限流即返回拒绝响应,避免资源过载,保障服务稳定性。

三、为什么不用网关?我们选择"内嵌式限流"

市面上常见的限流实现主要有两类:独立网关型内嵌式组件型。在深入评估后,决定自研轻量级内嵌方案,原因如下:

1. 网关太"重":功能过剩,性能多一跳

像 Spring Cloud Gateway 这类微服务网关,虽然内置限流能力,但它本质是一个全能型流量中枢,集成了路由、鉴权、协议转换、日志审计等大量功能。

❌ 对我们当前"只做限流"的需求来说,引入网关就像为了装个门铃而重建整栋楼------成本高、链路长、运维复杂。 ✅ 内嵌方案直接在业务进程内完成判断,仅需一次 Redis 调用,延迟更低、路径更短、风险更小

2. 开源组件如 Sentinel:功能强大,但"杀鸡用牛刀"

阿里开源的 Sentinel 是业界优秀的内嵌式流量治理框架,支持限流、熔断、系统自适应保护等高级能力。

但我们发现:

  • Sentinel 是产品级平台,包含控制台、指标采集、规则持久化、集群流控等模块;
  • 要完整发挥其能力,需额外部署 Dashboard、对接数据源、维护心跳上报等,运维和集成成本较高
  • 而我们的核心诉求非常明确:只需基础的分布式限流 + 动态规则更新,并不需要复杂的熔断联动或实时监控大盘。

🛠️ 与其引入一个"瑞士军刀",不如打造一把趁手的"水果刀"------轻量、专注、易维护

3. 自研内嵌方案的优势

维度 网关方案 Sentinel 等开源方案 本方案(自研内嵌)
部署复杂度 高(需独立集群) 中(需控制台+Agent) 低(仅依赖 Redis+ZK)
侵入性 无(但链路变长) 低(需埋点/注解) 零侵入(Starter + 注解)
控制粒度 URL/Header 级 方法/资源级 方法级 + 上下文(IP/用户ID)
规则生效速度 依赖网关刷新 秒级(需推送) 秒级(ZK Watcher 实时监听)
可维护性 公司级统一维护 社区+自维护 团队自主可控,代码透明

💡 结论 :当限流需求简单、稳定、且希望快速迭代时,轻量自研 > 重型开源 > 网关集成

四、核心技术实现

4.1 Redis 限流:用 Lua 脚本守住"时间窗口"

我们使用 Redis 的 INCR + EXPIRE 原子操作(通过 Lua 脚本保证),实现固定时间窗口限流。若返回值 > 阈值,则返回false,拒绝请求。

java 复制代码
import redis.clients.jedis.Jedis;
import java.util.Collections;
​
public class RateLimiter {
​
    private final Jedis jedis;
    private final String rateLimitKeyPrefix = "rate_limit:"; // Redis key 前缀
    private final int windowSizeInSeconds; // 时间窗口大小(秒)
    private final long limit; // 允许的最大请求数
​
    public RateLimiter(String redisHost, int redisPort, int windowSizeInSeconds, long limit) {
        this.jedis = new Jedis(redisHost, redisPort);
        this.windowSizeInSeconds = windowSizeInSeconds;
        this.limit = limit;
    }
​
    /**
     * 尝试访问资源,如果超过限制则返回 false。
     */
    public boolean tryAccess(String identifier) {
        String key = rateLimitKeyPrefix + identifier;
        long currentTime = System.currentTimeMillis() / 1000L; // 转换为秒级时间戳
​
        // 使用 Lua 脚本来保证原子性操作
        String script =
                "local current\n" +
                        "current = redis.call('incr', KEYS[1])\n" +
                        "if tonumber(current) == 1 then\n" +
                        "   redis.call('expire', KEYS[1], ARGV[1])\n" +
                        "end\n" +
                        "return current";
​
        Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(windowSizeInSeconds)));
        if (result instanceof Long) {
            return (Long) result <= limit;
        } else {
            throw new RuntimeException("Unexpected response type from Redis");
        }
    }
​
    public void close() {
        jedis.close();
    }
​
    public static void main(String[] args) {
        RateLimiter limiter = new RateLimiter("localhost", 6379, 60, 5); // 每分钟最多5次请求
        String userIdentifier = "user:123"; // 用户标识符或IP地址等
​
        for (int i = 0; i < 7; i++) { // 尝试发起7次请求
            boolean allowed = limiter.tryAccess(userIdentifier);
            System.out.println("Attempt " + (i + 1) + ": " + (allowed ? "Allowed" : "Denied"));
        }
​
        limiter.close();
    }
}
  • 若返回值 ≤ 阈值 → 放行;
  • 若 > 阈值 → 拒绝(返回 429)。

4.2 ZooKeeper:动态规则中心

所有限流规则存于 ZK 节点,结构如下:

bash 复制代码
/rate-limit/config
├── /global          → {"limit": 1000, "windowSeconds": 60}
├── /client/clientA  → {"limit": 100, ...}
└── /api/user_get    → {"limit": 20, "enabled": true}

应用启动时加载规则,并通过 Curator 监听节点变化 ,规则更新后秒级生效,无需重启。

🔒 即使 ZK 宕机,应用仍使用最后一次加载的规则,不影响服务运行。


4.3 零侵入集成:Starter + 注解 + AOP

(1)业务只需加注解:
less 复制代码
@GetMapping("/user/info")
@RateLimited(value = "api:/user/info", fallbackCode = 429)
public User getUserInfo() {
    return userService.get();
}
(2)框架自动拦截:
  • AOP 切面监听 @RateLimited 注解;
  • 自动拼接限流 Key(如 api:/user/info:ip:192.168.1.100);
  • 查询规则 → 调 Redis → 决策放行或拒绝。

🌟 同时支持 Filter 全局拦截 (如对所有 /api/** 统一限流)。


4.4 容错兜底:系统挂了也不能拖垮业务!

故障组件 应对策略
Redis 不可用 可配置降级策略: • Fail-open:放行(非关键接口) • Fail-close:拒绝(关键接口) → 同时记录告警日志
ZooKeeper 不可用 使用内存中缓存的最后有效规则,继续工作

原则:限流是辅助功能,不能成为单点故障源。


4.5 日志与可观测性

每次限流决策都会记录结构化日志,示例:

json 复制代码
{
  "timestamp": "2025-11-14T18:30:00",
  "ruleKey": "api:/user/info",
  "clientIp": "192.168.1.100",
  "userId": "u12345",
  "result": "BLOCKED"
}
  • 日志通过 SLF4J + Logback 输出;
  • 可接入 ELK、SLS 等日志平台;
  • 关键事件可异步投递 RocketMQ,避免阻塞主线程。

五、使用方式:三步搞定

  1. 引入 Starter 依赖

    xml 复制代码
    <dependency>
        <groupId>com.yourcompany</groupId>
        <artifactId>rate-limit-starter</artifactId>
    </dependency>
  2. 配置 Redis & ZooKeeper 地址

    yaml 复制代码
    rate-limit:
      redis-host: localhost
      zk-connect: zk1:2181,zk2:2181
  3. 在方法上加注解

    typescript 复制代码
    @RateLimited("api:/order/create")
    public Order createOrder(...) { ... }

✅ 完成!无需改动一行业务逻辑。


六、总结:轻量、可靠、易用

优势 说明
轻量级 无独立部署,无额外网络跳转
高扩展 支持全局/接口/客户端多维度限流
强实时 ZK 推送,规则秒级生效
高可用 Redis/ZK 故障自动降级
零侵入 注解驱动,业务无感知

🚦 让流量有序通行,而不是一拥而上------这就是我们给每个接口装上的"智能红绿灯"。


当前方案比较简单轻量,如需进一步扩展(如滑动窗口、令牌桶、熔断联动等),可在当前架构上平滑演进。

相关推荐
Hui Baby2 小时前
springboot读取配置文件
后端·python·flask
leo_messi942 小时前
2026版商城项目(三)-- ES+认证服务
后端·python·django
Hadoop_Liang3 小时前
构建Spring Boot项目Docker镜像
spring boot·后端·docker
自珍JAVA3 小时前
Gobrs-Async 框架
后端
xdscode3 小时前
Spring 依赖注入方式全景解析
java·后端·spring
青柠代码录3 小时前
【Spring】@Component VS @Configuration
后端
喵个咪4 小时前
go-wind-cms 微服务架构设计:为什么基于 Kratos?
后端·微服务·cms
神奇小汤圆4 小时前
百度面试官:Redis 内存满了怎么办?你有想过吗?
后端
喵个咪4 小时前
Headless 架构优势:内容与展示解耦,一套 API 打通全端生态
前端·后端·cms