Java限流方法常见实现方案(单机限流和分布式限流)
作者:hqxstudying
Java限流用于保护系统资源,分为单机(Guava/滑动窗口)和分布式(Redis+Lua)方案,核心算法包括固定窗口、令牌桶、漏桶等,推荐使用Sentinel等成熟框架实现动态流量控制,本文介绍Java限流方法常见实现方案(单机限流和分布式限流),感兴趣的朋友一起看看吧
在 Java 项目中限制短时间内的频繁访问(即接口限流),是保护系统资源、防止恶意攻击或高频请求导致过载的重要手段。常见实现方案可分为单机限流和分布式限流,以下是具体实现方式:
一、核心限流算法
无论哪种方案,底层通常基于以下算法:
- 固定窗口计数器:将时间划分为固定窗口(如 1 秒),统计窗口内请求数,超过阈值则拒绝。
- 优点:简单易实现;缺点:窗口交界处可能出现 “突增流量”(如窗口边缘允许双倍阈值请求)。
- 滑动窗口:将固定窗口拆分为多个小窗口,实时滑动计算请求数,解决临界问题。
- 令牌桶:匀速生成令牌放入桶中,请求需获取令牌才能处理,支持突发流量(桶内令牌可累积)。
- 漏桶:请求先进入桶中,系统以固定速率处理,平滑流量波动(不支持突发流量)。
二、单机限流实现(适用于单实例服务)
1. 基于 Guava 的 RateLimiter(令牌桶算法)
Guava 提供了现成的RateLimiter
工具类,适合快速实现单机限流。
依赖引入:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency>
代码示例(结合 Spring 拦截器):
// 1. 定义限流拦截器 public class RateLimitInterceptor implements HandlerInterceptor { // 每秒允许10个请求(令牌桶算法) private final RateLimiter rateLimiter = RateLimiter.create(10.0); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 尝试获取令牌,无令牌则拒绝 if (!rateLimiter.tryAcquire()) { response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); response.getWriter().write("请求过于频繁,请稍后再试"); return false; } return true; } } // 2. 注册拦截器(Spring配置) @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 对指定路径生效(如所有接口) registry.addInterceptor(new RateLimitInterceptor()) .addPathPatterns("/**"); } }
2. 基于滑动窗口的自定义实现
适合需要更精细控制的场景(如按 IP 限流):
public class SlidingWindowLimiter { // 窗口大小(毫秒) private final long windowSize; // 窗口内最大请求数 private final int maxRequests; // 记录每个时间片的请求数(key:时间片起始时间,value:请求数) private final ConcurrentHashMap<Long, Integer> timeSliceCounts = new ConcurrentHashMap<>(); public SlidingWindowLimiter(long windowSize, int maxRequests) { this.windowSize = windowSize; this.maxRequests = maxRequests; } public synchronized boolean tryAcquire() { long now = System.currentTimeMillis(); // 计算当前窗口的起始时间 long windowStart = now - windowSize; // 移除过期的时间片 timeSliceCounts.keySet().removeIf(timestamp -> timestamp < windowStart); // 统计当前窗口总请求数 int totalRequests = timeSliceCounts.values().stream().mapToInt(Integer::intValue).sum(); if (totalRequests < maxRequests) { // 记录当前时间片的请求(精确到100ms,可调整精度) long currentSlice = now - (now % 100); timeSliceCounts.put(currentSlice, timeSliceCounts.getOrDefault(currentSlice, 0) + 1); return true; } return false; } } // 使用示例(在Controller中) @RestController public class TestController { // 10秒内最多允许5次请求(按IP限流) private final Map<String, SlidingWindowLimiter> ipLimiters = new ConcurrentHashMap<>(); @GetMapping("/test") public String test(HttpServletRequest request) { String ip = request.getRemoteAddr(); // 为每个IP创建独立的限流器 SlidingWindowLimiter limiter = ipLimiters.computeIfAbsent(ip, k -> new SlidingWindowLimiter(10000, 5)); if (!limiter.tryAcquire()) { return "IP:" + ip + " 请求过于频繁"; } return "请求成功"; } }
三、分布式限流(适用于多实例集群)
单机限流无法跨服务实例共享状态,分布式场景需借助中间件(如 Redis)实现全局计数。
基于 Redis + Lua 脚本(滑动窗口算法)
利用 Redis 的原子性和 Lua 脚本保证限流逻辑的一致性:
Lua 脚本(限流逻辑):
-- 限流key(如:接口名:IP) local key = KEYS[1] -- 窗口大小(毫秒) local windowSize = tonumber(ARGV[1]) -- 最大请求数 local maxRequests = tonumber(ARGV[2]) -- 当前时间 local now = tonumber(ARGV[3]) -- 窗口起始时间 local windowStart = now - windowSize -- 移除窗口外的请求记录 redis.call('ZREMRANGEBYSCORE', key, 0, windowStart) -- 统计当前窗口内的请求数 local currentCount = redis.call('ZCARD', key) if currentCount < maxRequests then -- 记录当前请求时间戳 redis.call('ZADD', key, now, now .. ':' .. math.random()) -- 设置key过期时间(避免内存泄漏) redis.call('EXPIRE', key, windowSize / 1000 + 1) return 1 -- 允许请求 end return 0 -- 拒绝请求
Java 代码调用:
@Component public class RedisRateLimiter { @Autowired private StringRedisTemplate redisTemplate; // 加载Lua脚本 private final DefaultRedisScript<Long> limitScript; public RedisRateLimiter() { limitScript = new DefaultRedisScript<>(); limitScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limit.lua"))); limitScript.setResultType(Long.class); } /** * 尝试获取请求权限 * @param key 限流标识(如:"api:test:192.168.1.1") * @param windowSize 窗口大小(毫秒) * @param maxRequests 最大请求数 * @return 是否允许 */ public boolean tryAcquire(String key, long windowSize, int maxRequests) { Long result = redisTemplate.execute( limitScript, Collections.singletonList(key), String.valueOf(windowSize), String.valueOf(maxRequests), String.valueOf(System.currentTimeMillis()) ); return result != null && result == 1; } } // 在Controller中使用 @RestController public class TestController { @Autowired private RedisRateLimiter redisRateLimiter; @GetMapping("/test") public String test(HttpServletRequest request) { String ip = request.getRemoteAddr(); String key = "api:test:" + ip; // 10秒内最多5次请求 boolean allowed = redisRateLimiter.tryAcquire(key, 10000, 5); if (!allowed) { return "请求过于频繁,请稍后再试"; } return "请求成功"; } }
四、成熟框架推荐
生产环境中,推荐使用现成的限流框架简化开发:
- Sentinel:阿里开源的流量控制框架,支持限流、熔断、降级,可通过注解或配置中心动态调整规则。
- Resilience4j:轻量级熔断限流框架,支持令牌桶、滑动窗口等多种算法,适合 Spring Boot 项目。
- Spring Cloud Gateway:网关层限流(如基于 Redis 的
RequestRateLimiter
过滤器),适合在入口层统一限流。
总结
- 单机服务:优先使用 Guava 的
RateLimiter
或自定义滑动窗口(简单场景)。 - 分布式服务:必须基于 Redis 等中间件实现全局限流,配合 Lua 脚本保证原子性。
- 复杂场景:直接集成 Sentinel 等成熟框架,减少重复开发并支持动态配置。
根据业务需求(如限流粒度:IP / 用户 / 接口、是否允许突发流量)选择合适的方案即可。
到此这篇关于JAVA限流方法的文章就介绍到这了,更多相关java限流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!