基于Redis分布式的限流
以下是基于 Redis 实现分布式限流的 Java 解决方案,包含多种限流算法和完整实现代码:
一、限流算法选择与实现
1. 固定窗口算法(Simple Rate Limiter)
public class RedisFixedWindowRateLimiter {private final StringRedisTemplate redisTemplate;private final String script = "local current = redis.call('INCR', KEYS[1])\n" +"if current == 1 then\n" +" redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +"end\n" +"return current > tonumber(ARGV[2])";public RedisFixedWindowRateLimiter(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}/*** 固定窗口限流* @param key 限流标识* @param period 时间窗口(秒)* @param limit 最大请求数* @return 是否允许请求*/public boolean isAllowed(String key, int period, int limit) {key = "rate_limit:" + key;return !redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),Collections.singletonList(key),String.valueOf(period),String.valueOf(limit));}
}
2. 滑动窗口算法(基于ZSet)
public class RedisSlidingWindowRateLimiter {private final StringRedisTemplate redisTemplate;private final RedisScript<Long> script;public RedisSlidingWindowRateLimiter(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;this.script = new DefaultRedisScript<>("local key = KEYS[1]\n" +"local now = tonumber(ARGV[1])\n" +"local window = tonumber(ARGV[2])\n" +"local limit = tonumber(ARGV[3])\n" +"local start = now - window\n" +// 移除窗口外的记录"redis.call('ZREMRANGEBYSCORE', key, 0, start)\n" +// 统计窗口内请求数"local count = redis.call('ZCARD', key)\n" +// 判断是否超出限制"local allowed = count < limit\n" +// 若允许则添加当前请求时间戳"if allowed then redis.call('ZADD', key, now, now) end\n" +// 设置过期时间,避免冷key永久存在"redis.call('EXPIRE', key, window)\n" +"return allowed and 1 or 0",Long.class);}/*** 滑动窗口限流* @param key 限流标识* @param window 窗口大小(毫秒)* @param limit 窗口内最大请求数* @return 是否允许请求*/public boolean isAllowed(String key, long window, int limit) {key = "sliding_window:" + key;long now = System.currentTimeMillis();return redisTemplate.execute(script, Collections.singletonList(key),String.valueOf(now),String.valueOf(window),String.valueOf(limit)) == 1L;}
}
3. 令牌桶算法(Token Bucket)
public class RedisTokenBucketRateLimiter {private final StringRedisTemplate redisTemplate;private final RedisScript<List<Long>> script;public RedisTokenBucketRateLimiter(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;this.script = new DefaultRedisScript<>("local tokens_key = KEYS[1]\n" +"local timestamp_key = KEYS[2]\n" +"local rate = tonumber(ARGV[1])\n" + // 令牌生成速率(个/秒)"local capacity = tonumber(ARGV[2])\n" + // 桶容量"local now = tonumber(ARGV[3])\n" + // 当前时间戳(毫秒)"local requested = tonumber(ARGV[4])\n" + // 请求的令牌数"local last_tokens = tonumber(redis.call('GET', tokens_key) or capacity)\n" +"local last_refreshed = tonumber(redis.call('GET', timestamp_key) or 0)\n" +"local delta = math.max(0, now - last_refreshed)\n" + // 距离上次刷新的时间差"local filled_tokens = math.min(capacity, last_tokens + (delta * rate / 1000))\n" + // 计算当前令牌数"local allowed = filled_tokens >= requested\n" + // 是否允许请求"local new_tokens = filled_tokens\n" +"if allowed then\n" +" new_tokens = filled_tokens - requested\n" +"end\n" +"redis.call('SET', tokens_key, new_tokens, 'EX', 3600)\n" + // 设置1小时过期"redis.call('SET', timestamp_key, now, 'EX', 3600)\n" +"return { allowed and 1 or 0, new_tokens }",List.class);}/*** 令牌桶限流* @param key 限流标识* @param rate 令牌生成速率(个/秒)* @param capacity 桶容量* @param requested 请求的令牌数(通常为1)* @return 是否允许请求*/public boolean isAllowed(String key, int rate, int capacity, int requested) {key = "token_bucket:" + key;String tokensKey = key + ":tokens";String timestampKey = key + ":timestamp";long now = System.currentTimeMillis();List<Long> result = redisTemplate.execute(script, Arrays.asList(tokensKey, timestampKey),String.valueOf(rate),String.valueOf(capacity),String.valueOf(now),String.valueOf(requested));return result != null && result.get(0) == 1L;}
}
二、注解式限流实现(基于Spring AOP)
1. 自定义限流注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {String key() default "rate_limit";int period() default 60; // 时间窗口(秒)int limit() default 100; // 最大请求数RateLimitType type() default RateLimitType.COMMON; // 限流类型
}public enum RateLimitType {COMMON, // 普通限流IP, // 基于IP限流USER // 基于用户限流
}
2. AOP切面实现
@Aspect
@Component
public class RateLimitAspect {private final RedisFixedWindowRateLimiter rateLimiter;public RateLimitAspect(StringRedisTemplate redisTemplate) {this.rateLimiter = new RedisFixedWindowRateLimiter(redisTemplate);}@Around("@annotation(com.example.ratelimit.RateLimit)")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();RateLimit rateLimit = method.getAnnotation(RateLimit.class);String key = getKey(rateLimit, joinPoint);boolean allowed = rateLimiter.isAllowed(key, rateLimit.period(), rateLimit.limit());if (!allowed) {throw new RuntimeException("请求过于频繁,请稍后再试");}return joinPoint.proceed();}private String getKey(RateLimit rateLimit, ProceedingJoinPoint joinPoint) {switch (rateLimit.type()) {case IP:return getClientIp() + "_" + rateLimit.key();case USER:return getUserId() + "_" + rateLimit.key();default:return methodSignatureToKey(joinPoint);}}// 获取客户端IP(实际实现需从Request中获取)private String getClientIp() {// 实现略return "127.0.0.1";}// 获取用户ID(实际实现需从SecurityContext或Session中获取)private String getUserId() {// 实现略return "user_123";}private String methodSignatureToKey(ProceedingJoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();return signature.getDeclaringTypeName() + "." + signature.getName();}
}
三、限流熔断与降级策略
1. 熔断降级处理器
@Component
public class RateLimitFallbackHandler {/*** 默认降级方法(可自定义返回值)*/public Object defaultFallback(JoinPoint joinPoint, Throwable ex) {return Result.fail("系统繁忙,请稍后再试", 503);}
}
2. 整合Sentinel实现熔断降级
@Service
public class UserService {@RateLimit(key = "getUser", limit = 200)@SentinelResource(value = "getUser", fallback = "getUserFallback")public User getUser(Long userId) {// 业务逻辑return userDao.getUserById(userId);}public User getUserFallback(Long userId, Throwable ex) {// 限流或熔断后的降级逻辑return new User(-1L, "系统繁忙,请稍后再试");}
}
四、配置与使用示例
1. Redis配置
@Configuration
public class RedisConfig {@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new StringRedisSerializer());return template;}
}
2. 使用注解限流
@RestController
@RequestMapping("/api")
public class ApiController {@GetMapping("/data")@RateLimit(key = "getData", period = 60, limit = 100)public ResponseData getData() {// 业务逻辑return ResponseData.success();}
}
五、高级特性与优化建议
1. 限流指标监控
@Component
public class RateLimitMetrics {private final StringRedisTemplate redisTemplate;private final MeterRegistry meterRegistry;public RateLimitMetrics(StringRedisTemplate redisTemplate, MeterRegistry meterRegistry) {this.redisTemplate = redisTemplate;this.meterRegistry = meterRegistry;}/*** 收集限流指标*/@Scheduled(fixedDelay = 60_000)public void collectMetrics() {Set<String> keys = redisTemplate.keys("rate_limit:*");if (keys != null) {for (String key : keys) {String count = redisTemplate.opsForValue().get(key);if (count != null) {String metricName = key.replace("rate_limit:", "ratelimit.");meterRegistry.gauge(metricName, Long.parseLong(count));}}}}
}
2. 动态限流规则
@Service
public class DynamicRateLimitService {private final StringRedisTemplate redisTemplate;private final Map<String, RateLimitConfig> configMap = new ConcurrentHashMap<>();@PostConstructpublic void init() {// 从配置中心加载规则loadRulesFromConfigCenter();// 注册配置变更监听器registerConfigChangeListener();}public boolean isAllowed(String key) {RateLimitConfig config = configMap.get(key);if (config == null) {return true;}return new RedisFixedWindowRateLimiter(redisTemplate).isAllowed(key, config.getPeriod(), config.getLimit());}
}
六、不同限流算法对比与选择
算法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
固定窗口 | 实现简单、性能高 | 临界问题(突刺现象) | 对精度要求不高的场景 |
滑动窗口 | 解决临界问题 | 占用内存较大 | 需要更精确限流的场景 |
令牌桶 | 支持突发流量、平滑限流 | 实现复杂 | 对流量平滑度有要求的场景 |
七、注意事项与最佳实践
-
性能优化:
- 使用 Lua 脚本保证原子性
- 批量操作减少网络开销
- 合理设置过期时间避免冷key占用内存
-
异常处理:
- Redis连接异常时的降级策略(如允许请求或拒绝所有请求)
- 熔断机制防止级联故障
-
监控与告警:
- 监控限流命中率、QPS、拒绝率等指标
- 设置阈值告警(如拒绝率超过50%时触发)
-
分布式一致性:
- 多节点部署时确保时间同步(NTP服务)
- 优先选择Redis Cluster或哨兵模式保证高可用
通过以上实现,你可以基于 Redis 构建高性能、高可用的分布式限流系统,有效保护后端服务免受流量冲击。