如何为Spring Cloud Gateway实现按用户订阅区分的API限流
基于用户订阅等级的Spring Cloud Gateway动态限流实现
要实现同一API根据用户订阅等级使用不同限流规则,核心是动态选择对应的RedisRateLimiter实例,而非固定绑定某一个。以下是具体实现步骤:
1. 定义订阅等级标识
先统一用户订阅等级的枚举,方便后续逻辑判断:
public enum SubscriptionLevel { CONSUMER_50TPS, CONSUMER_100TPS }
2. 实现用户等级获取服务
编写服务类,根据用户名获取对应的订阅等级(实际可从数据库、Redis缓存或JWT Claims中读取):
@Service public class UserSubscriptionService { // 示例逻辑:根据用户名返回订阅等级,实际替换为业务逻辑 public SubscriptionLevel getSubscriptionLevel(String username) { // 比如VIP用户前缀匹配返回100TPS等级,普通用户返回50TPS return username.startsWith("vip_") ? SubscriptionLevel.CONSUMER_100TPS : SubscriptionLevel.CONSUMER_50TPS; } }
3. 自定义动态限流处理器
创建一个动态RateLimiter,根据用户等级自动切换对应的RedisRateLimiter实例:
@Component public class DynamicRedisRateLimiter implements RateLimiter<String> { private final RedisRateLimiter consumer50tps; private final RedisRateLimiter consumer100tps; private final UserSubscriptionService subscriptionService; // 通过构造注入获取已定义的两个限流实例和等级服务 public DynamicRedisRateLimiter(RedisRateLimiter consumer50tps, RedisRateLimiter consumer100tps, UserSubscriptionService subscriptionService) { this.consumer50tps = consumer50tps; this.consumer100tps = consumer100tps; this.subscriptionService = subscriptionService; } @Override public Mono<Response> isAllowed(String routeId, String username) { // 根据用户名获取等级,选择对应的限流规则 SubscriptionLevel level = subscriptionService.getSubscriptionLevel(username); RateLimiter<String> targetLimiter = switch (level) { case CONSUMER_100TPS -> consumer100tps; default -> consumer50tps; }; // 调用对应限流实例的判断逻辑 return targetLimiter.isAllowed(routeId, username); } }
4. 修改网关路由配置
将原来固定的consumer50tps替换为自定义的动态限流处理器:
@Bean public RouteLocator appRouteConfig(RouteLocatorBuilder builder, DynamicRedisRateLimiter dynamicRedisRateLimiter, KeyResolver userKeyResolver) { return builder.routes() .route(p -> p.path("/microservice-one/api1/**") .filters(f -> f .rewritePath("/microservice-one/(?<segment>.*)", "/${segment}") .requestRateLimiter(r -> r.setRateLimiter(dynamicRedisRateLimiter) .setKeyResolver(userKeyResolver) // 保留原有用户名提取逻辑 )) .uri("lb://microservice-one")) .build(); }
5. 保留原有基础配置
原有的RedisRateLimiter实例和用户KeyResolver继续保留:
@Bean public RedisRateLimiter consumer50tps() { return new RedisRateLimiter(50, 50, 1); } @Bean public RedisRateLimiter consumer100tps() { return new RedisRateLimiter(100, 100, 1); } @Bean public KeyResolver userKeyResolver() { return exchange -> { // 保留原有从JWT提取用户名的逻辑,示例如下 String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String jwtToken = authHeader.substring(7); // 使用JWT解析库(如JJWT)解析获取用户名 Claims claims = Jwts.parser() .setSigningKey("your_jwt_secret_key") .parseClaimsJws(jwtToken) .getBody(); return Mono.just(claims.getSubject()); } return Mono.empty(); }; }
关键注意事项
- 性能优化:用户订阅等级建议缓存到Redis,避免每次请求都查询数据库,降低延迟
- JWT扩展:如果JWT中已包含订阅等级字段,可直接从Claims中提取,省去服务调用
- 扩展性:后续新增等级只需在
SubscriptionLevel枚举中添加,并在动态限流处理器中补充对应分支即可
内容的提问来源于stack exchange,提问作者Imranmadbar
相关产品推荐
相关产品推荐

