1、SpringGateWay
在Spring Cloud Gateway中实现限流的一个常见方法是使用Redis RateLimiter。Spring Cloud Gateway提供了内置的RateLimiter API,它可以配合Redis使用,实现对请求的限流。以下是使用Redis RateLimiter进行限流的基本步骤:
1. 引入依赖
首先,确保你的项目中引入了Spring Cloud Gateway和Spring Boot Redis的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
确保已经安装配置好Redis服务,Spring Cloud Gateway将利用这个服务进行限流数据的存储与查询。
2. 配置Redis信息
在application.yml或application.properties中配置你的Redis信息:
spring:
redis:
host: localhost
port: 6379
3. 配置限流规则
在application.yml
中配置Spring Cloud Gateway的路由规则,并定义Redis RateLimiter作为限流策略。以下示例展示了如何配置一个简单的限流规则:
spring:
cloud:
gateway:
routes:
- id: my_route_id
uri: http://example.com
predicates:
- Path=/api/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"
在上述配置中,replenishRate
和burstCapacity
分别控制了令牌桶的填充速率和容量,key-resolver
用于定义如何解析限流的键(比如根据客户端IP地址或用户标识进行限流)。
4. 定义Key Resolver
key-resolver
是一个指向Spring Bean的引用,这个Bean需要实现KeyResolver
接口,用于决定限流规则的作用对象(如用户、IP等)。以下是一个按请求路径进行限流的KeyResolver
示例:
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Configuration
public class RateLimiterConfiguration {
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
}
5. 启动并测试
启动你的Spring Boot应用并测试限流。当请求的频率超过了replenishRate
定义的阈值时,客户端将会收到429 Too Many Requests的响应。
以上就是在Spring Cloud Gateway中使用Redis RateLimiter进行限流的基本步骤。根据你的实际需求,可能还需要对限流策略进行更细致的调整,包括混合使用不同的KeyResolver
来实现复杂的限流逻辑等。
2、ZUUL
需要借助其他组件或服务来达到限流目的,比如Netflix的Hystrix、阿里巴巴的Sentinel或自定义过滤器结合第三方缓存服务(例如Redis)。
以下是使用Netflix Hystrix和自定义Zuul过滤器实现限流的一种方式:
1. 添加Hystrix依赖:
如果你的项目中尚未包含Hystrix,请首先添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2. 开启Hystrix和Hystrix Dashboard(可选):
确保你的应用中启用了Hystrix和Hystrix的监控面板(Dashboard):
@EnableZuulProxy
@EnableCircuitBreaker
@EnableHystrixDashboard // 如果你想开启监控面板
@SpringBootApplication
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
}
3. 创建一个自定义的Zuul过滤器:
创建一个Zuul过滤器来实现限流逻辑,你可以使用Hystrix来控制并发量。
public class RateLimitZuulFilter extends ZuulFilter {
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100); // 每秒100个请求的速率
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return -4; // 设置为高优先级
}
@Override
public boolean shouldFilter() {
return true; // 永远开启过滤
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
if (!RATE_LIMITER.tryAcquire()) {
ctx.setSendZuulResponse(false); // 限流发生时,拒绝访问
ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value()); // 返回429状态码
ctx.setResponseBody("Too many requests"); // 设置返回的内容
response.setContentType("application/json;charset=UTF-8");
}
return null;
}
}
在上面的示例中,我们使用了com.google.common.util.concurrent.RateLimiter
类(来自Guava库)作为一个简单的令牌桶实现方式。
4. 注册Zuul过滤器:
在Spring Application中注册上面创建的Zuul过滤器。
@Bean
public RateLimitZuulFilter rateLimitZuulFilter() {
return new RateLimitZuulFilter();
}
完成以上步骤后,每当请求到达Zuul时,都会经过这个限流过滤器,如果超出了预定的限流标准,就会返回HTTP 429 - Too Many Requests响应。
对于更复杂的限流策略,比如基于用户或者IP进行限流,你需要在自定义过滤器中实现额外的逻辑,或者考虑使用如Sentinel这样的第三方限流组件。Sentinel可以与Spring Cloud Alibaba整合,提供更丰富的限流、降级和熔断策略。
3、使用Alibaba nacos sentinel
完全注解式+配置就可以搞定,他的底层原理也是些限流算法,见4。
4、手动写
纯手写限流器可以从多种算法思路出发,主要包括固定窗口计数器、滑动窗口日志、令牌桶和漏桶算法。每种算法都有其特定的应用场景和优缺点。以下分别介绍这些思路:
1. 固定窗口计数器(Fixed Window Counter)
原理:将时间分割成等长的窗口,每个窗口内维护一个计数器,记录该窗口内的请求次数。当请求到达时,增加计数器的值,如果计数器的值超过预设的阈值,则拒绝后续的请求直到下一个窗口开始。
优点:实现简单,性能较好。 缺点:时间边界上可能会出现请求的瞬时双倍放行,因为窗口切换时,计数器重置,导致限流不够平滑。
2. 滑动窗口日志(Sliding Window Log)
原理:记录每个请求的时间戳,在每次请求到来时,清理掉时间窗口外的请求记录,并检查当前窗口内的请求总数是否超限。
优点:限流更加平滑,避免了固定窗口算法的边界问题。 缺点:随着请求量的增加,内存和计算开销较大,因为需要存储窗口内所有请求的时间戳。
3. 令牌桶(Token Bucket)
原理:一个初始为空的桶,以固定的速率往桶里添加令牌,每个到来的请求都需要获取一个令牌。如果能够获取到令牌,则请求被处理;如果令牌不足,则请求被拒绝。
优点:可以应对突发流量,提供一定程度的弹性和平滑限流。 缺点:实现相对复杂,需要维护令牌的生成和消耗。
4. 漏桶(Leaky Bucket)
原理:请求先进入到一个桶里,然后以限定的速度从桶中流出处理。即使短时间内大量请求涌入,流出速度也是恒定的。
优点:输出流量非常平稳。 缺点:对于突发流量的响应不如令牌桶灵活。
令牌桶算法代码:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
// 令牌桶限流器类
public class TokenBucketLimiter {
// 当前令牌数量
private final AtomicInteger tokens;
// 令牌桶的容量
private final int capacity;
// 令牌的生成速率(每秒生成令牌数)
private final int tokenRate;
// 定时任务执行器,用于生成令牌
private final ScheduledExecutorService scheduler;
// 构造函数初始化令牌桶限流器
public TokenBucketLimiter(int capacity, int tokenRate) {
this.capacity = capacity; // 设置令牌桶容量
this.tokenRate = tokenRate; // 设置令牌生成速率
this.tokens = new AtomicInteger(0); // 初始化令牌数量为0
this.scheduler = Executors.newSingleThreadScheduledExecutor();
startTokenRefill(); // 开始补充令牌
}
// 开启定时补充令牌到桶中
private void startTokenRefill() {
// 定时以固定的速率放入令牌
scheduler.scheduleAtFixedRate(() -> {
if (tokens.get() < capacity) {
// 如果令牌数未达到上限,则添加令牌
tokens.incrementAndGet();
}
}, 0, 1000 / tokenRate, TimeUnit.MILLISECONDS); // 时间单位为毫秒
}
// 尝试从令牌桶中获取令牌
public boolean tryAcquire() {
// 令牌数大于0时,减少一个令牌,并返回true表示获取成功
while (true) {
int existingTokens = tokens.get();
if (existingTokens <= 0) {
return false; // 获取失败,没有足够的令牌
}
if (tokens.compareAndSet(existingTokens, existingTokens - 1)) {
return true; // 成功减少一个令牌
}
// CAS 更新失败,循环重试
}
}
// 应用程序结束时应该调用的清理方法,关闭定时任务执行器避免内存泄漏
public void stop() {
scheduler.shutdown();
}
// 演示如何使用令牌桶限流器
public static void main(String[] args) throws InterruptedException {
// 创建一个容量为10,每秒生成5个令牌的令牌桶
TokenBucketLimiter limiter = new TokenBucketLimiter(10, 5);
// 模拟请求
for (int i = 0; i < 20; i++) {
// 尝试获取令牌
if (limiter.tryAcquire()) {
System.out.println("Request allowed at " + System.currentTimeMillis());
} else {
System.out.println("Request denied at " + System.currentTimeMillis());
}
// 模拟请求的间隔时间
Thread.sleep(100);
}
//
在这个限流器中,我们使用了AtomicInteger
来确保线程安全地对令牌数量进行操作,并用compareAndSet
方法来预防并发时的问题。此外,我们添加了一个关闭方法shutdown()
用以关闭我们创建的定时器任务。
记得在实际生产环境中优雅地关闭ScheduledExecutorService
,以防止资源泄露。如果您打算在生产环境中部署这样的限流器,可能还需要增加更多的特性,如动态调整限流策略、监控与告警、记录日志等。
漏桶算法代码:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
// 漏桶限流器类
public class LeakyBucketLimiter {
// 当前桶中的水量,也就是目前的请求量
private final AtomicInteger water;
// 桶的容量限制,当水量(请求量)超过容量时将不再接受新的请求
private final int capacity;
// 水的漏出速率(请求处理速率),以每秒固定数量的请求处理速度
private final int leakRate;
// 定时任务执行器,用于以一定的速率漏水(处理请求)
private final ScheduledExecutorService scheduler;
public LeakyBucketLimiter(int capacity, int leakRate) {
this.capacity = capacity; // 设置桶的容量
this.leakRate = leakRate; // 设置漏水速率,即每秒可以处理的请求数
this.water = new AtomicInteger(0); // 初始化水量(请求量)为0
this.scheduler = Executors.newSingleThreadScheduledExecutor();
startLeakage(); // 开始模拟漏水(请求处理)
}
// 开始水的漏出过程(开始处理请求的过程)
private void startLeakage() {
scheduler.scheduleAtFixedRate(() -> {
if (water.get() > 0) {
// 桶内有水的情况下,每间隔固定时间就漏一滴水(处理一个请求)
water.decrementAndGet();
}
}, 0, 1000 / leakRate, TimeUnit.MILLISECONDS); // 以固定频率减少水量
}
// 尝试将水(请求)放入桶中
public boolean tryAcquire() {
// 判断当前水量是否小于桶的容量
if (water.get() < capacity) {
// 若桶未满,则加入一滴水(接受一个请求)
// 并检查加水后是否溢出,若不溢出则表示请求被接受
return water.incrementAndGet() <= capacity;
}
// 若桶已满,则拒绝新的水滴(拒绝新的请求)
return false;
}
// 应用程序结束时应该调用的清理方法,关闭定时任务执行器避免内存泄漏
public void stop() {
scheduler.shutdown();
}
// 测试漏桶限流器
public static void main(String[] args) throws InterruptedException {
// 创建一个桶容量为10,漏水速率为每秒5的漏桶
LeakyBucketLimiter limiter = new LeakyBucketLimiter(10, 5);
// 模拟请求
for (int i = 0; i < 20; i++) {
// 尝试获取请求
if (limiter.tryAcquire()) {
System.out.println("Request allowed at " + System.currentTimeMillis());
} else {
System.out.println("Request denied at " + System.currentTimeMillis());
}
// 模拟请求间隔时间
Thread.sleep(100);
}
// 中止漏桶限流器
limiter.stop();
}
}
代码中的LeakyBucketLimiter
类实现了一个简单的漏桶算法的限流器。类的属性包括当前的水量(代表当前请求数量),桶的最大容量,以及漏水(实际处理请求)的速率。通过定时任务定期减少水量,模拟请求的处理。
当尝试添加一个新的请求时(tryAcquire
方法被调用),它首先检查桶的水量是否已满。如果没有,它允许新的水加入桶中(允许新请求进入),否则拒绝(拒绝新请求)。