在构建健壮的 Java 后端应用,尤其是微服务系统时,限流是不可或缺的一环。它像一道坚固的防洪堤,保护我们的服务免受流量洪峰的冲击,确保系统的稳定性和可用性。然而,限流的实现并非只有一种方式。根据应用架构和部署模式的不同,我们主要面临两种选择:单机限流和分布式限流。理解它们的差异、优劣和适用场景,对于做出正确的技术选型至关重要。
一、 单机限流:简单高效的本地卫士
什么是单机限流?
顾名思义,单机限流是指限流的状态和计算逻辑完全发生在单个应用程序实例(JVM 进程)内部。所有的计数器、令牌桶状态、时间窗口信息都存储在该实例的内存中。
它是如何工作的?
当请求进入单个应用实例时,该实例内的限流组件会检查内存中的状态(例如,当前窗口的计数、令牌桶中的令牌数),并根据预设的规则决定是放行请求还是拒绝请求。实例之间的限流状态是完全隔离的,互不影响。
优点:
- 简单性: 实现相对简单,无需依赖外部系统。
- 高性能: 所有计算都在本地内存中完成,没有网络开销,响应速度极快。
- 无外部依赖: 不需要引入额外的中间件(如 Redis)。
缺点:
- 无法应用于分布式/集群环境: 这是其致命弱点。如果你的应用部署了多个实例(水平扩展),每个实例只会在自己的内存中进行限流。假设限制是 100 QPS,部署了 5 个实例,那么总的限制实际上可能高达 500 QPS,远超预期,无法达到全局限流的目的。
- 状态易丢失: 限流状态存储在内存中,一旦实例重启,所有状态都会丢失,限流会从零开始,可能导致重启瞬间流量突增。
- 内存消耗: 如果限流规则非常复杂或需要维护大量 Key(例如按用户 ID 限流),可能会占用较多内存。
Java 中的常见工具:
-
Guava RateLimiter: 最经典的单机令牌桶实现,简单易用,性能卓越。
// 每秒允许 10 个请求 RateLimiter rateLimiter = RateLimiter.create(10.0); if (rateLimiter.tryAcquire()) { // 处理请求 } else { // 拒绝请求 }
-
Resilience4j RateLimiter: 另一个流行的容错库,其默认的限流器也是基于内存的单机实现。
-
简单的 AtomicInteger / Semaphore: 对于一些基础的计数或并发控制场景,也可以直接使用 JDK 的原子类或信号量。
适用场景:
- 单体应用或不需要水平扩展的服务。
- 部署实例非常少,且能接受总体限制是单实例限制乘以实例数的场景。
- 对性能要求极高,且可以容忍非全局精确限流的内部工具或特定场景。
二、 分布式限流:协同作战的全局屏障
什么是分布式限流?
分布式限流是指限流的状态被集中存储在外部共享系统中(如 Redis),所有应用实例通过访问这个共享系统来获取和更新限流状态,从而实现跨多个实例的全局限制。
它是如何工作的?
当请求到达任何一个应用实例时,该实例会:
- 连接到外部共享存储(如 Redis)。
- 根据限流 Key(例如 rate_limit:userId:api_path)查询当前状态(计数、令牌数等)。
- 执行限流算法逻辑(通常在共享存储端通过原子操作完成,如 Lua 脚本)。
- 根据共享存储返回的结果决定放行或拒绝。
核心挑战:
维护一个跨实例共享、实时更新(分布式状态同步: 这是最大的挑战。单个服务的多个实例如何共享同一个限流计数器/状态? 解决方案: 必须使用外部集中式存储,如 Redis (最常用,性能好,有原子操作), Hazelcast, Ignite 等。这就是为什么 Guava RateLimiter 或 Resilience4j 的默认内存在微服务中不够用的原因。Bucket4j 对此有良好支持。)、且操作原子的限流状态。
优点:
- 全局限制: 能够精确地控制整个集群(所有实例)的总请求速率,无论部署多少实例。
- 适用于微服务/集群: 是微服务架构和需要水平扩展的应用进行限流的标准方案。
- 状态持久化 (相对): 依赖的外部存储(如 Redis)通常具有持久化机制或高可用方案,相比内存更不容易丢失状态。
缺点:
- 增加复杂性: 需要引入并维护一个外部依赖(如 Redis 集群)。
- 性能开销: 每次请求都需要进行一次网络调用访问外部存储,相比本地内存操作会有额外的延迟。
- 依赖外部系统稳定性: 如果外部存储(如 Redis)出现故障,限流功能将失效,需要考虑降级或容错策略。
Java 中的常见方案/工具:
-
Redis + Lua 脚本: 这是最主流的分布式限流实现方式。
- 原因: Redis 性能极高,且 Lua 脚本能保证多个 Redis 命令的原子性执行,完美解决了分布式环境下的并发竞争问题。
- 实现: 可以用 Lua 实现前面讨论过的任何算法(固定窗口、滑动日志、漏桶、令牌桶)。Spring 应用中通常通过 RedisTemplate 执行 Lua 脚本。
-
Bucket4j: 一个功能强大的 Java 限流库,其核心优势在于内置了对多种分布式后端的支持,包括 Redis (Lettuce/Jedis)、Hazelcast, Ignite, JCache 等。它封装了与后端交互的细节,使用更方便。
-
API 网关 (如 Spring Cloud Gateway): 网关是实现分布式限流的理想位置。Spring Cloud Gateway 提供了基于 Redis 的 RequestRateLimiter Filter,只需简单配置即可实现按用户、IP、URL 等维度的分布式限流。
-
其他分布式协调服务: 如 ZooKeeper, Etcd 也可以用来实现分布式限流,但通常不如 Redis 在性能和便捷性上有优势。
适用场景:
- 微服务架构。
- 需要水平扩展(部署多个实例)的 Web 应用或服务。
- 需要对整个服务或特定用户/API 进行精确全局速率控制的场景。
- 公共 API 服务。