概述
限流是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。在突发情况下,平台/系统面对大并发大流量请求时,瞬时大流量会直接将系统打垮,无法对外提供服务。限流是针对这种场景最常见的解决方案之一,当平台/系统的请求达到一定并发数或速率,便进行等待、排队、降级、拒绝服务等。目前实现限流有两种常用模式:基于Guava实现单机令牌桶限流、基于Redis实现分布式限流。这里主要介绍下如何将这两种限流模式整合到一起,提供能力给用户按需灵活切换。
整合思路
抽象一个通用的限流器模块,在业务模块中引入,支持通过配置文件切换不同的限流模式,定义limit.mod = local ,表示启用本地Guava限流模式,定义limit.mod = remote,表示启用远程分布式限流模式。
整合步骤
1、创建通用模块common-limiter
在父项目下创建通用模块common-limiter服务,并在pom中引入如下相关依赖
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2、限流接口实现
2.1、创建限流接口
public interface LimiterMgr {
boolean tryExcute(Limiter limiter);
}
2.2、GLimiter:Guava限流的关键实现
@Slf4j
public class GLimiter implements LimiterMgr{
private final Map<String, RateLimiter> limiterMap = Maps.newConcurrentMap();
@Override
public boolean tryExcute(Limiter limiter) {
RateLimiter rateLimiter = getRateLimiter(limiter);
if (null == rateLimiter) {
return false;
}
boolean access = rateLimiter.tryAcquire(1,100, TimeUnit.MILLISECONDS);
return access;
}
}
2.3、RLimiter:Redis限流的关键实现
@Slf4j
public class RLimiter implements LimiterMgr{
private final StringRedisTemplate strRedisTpl;
public RLimiter(StringRedisTemplate strRedisTpl) {
this.strRedisTpl = strRedisTpl;
}
@Override
public boolean tryExcute(Limiter limiter) {
String key = limiter.getKey();
if (StringUtils.isEmpty(key)) {
throw new LimiterException( "key cannot be null" );
}
List<String> keys = new ArrayList<>();
keys.add( key );
// ......
return true;
}
}
3、创建配置类
编写对应配置类,根据配置文件注入限流实现类,当配置文件中属性 limit.mod = local 时启用Guava限流,当limit.mod = remote 时启用Redis限流
@Configuration
public class LimiterConfigure {
@Bean
@ConditionalOnProperty(name = "limit.mod",havingValue = "local")
public LimiterMgr gLimiter(){
return new GLimiter();
}
@Bean
@ConditionalOnProperty(name = "limit.mod",havingValue = "remote")
public LimiterMgr rLimiter(StringRedisTemplate strRedisTpl){
return new RLimiter(strRedisTpl);
}
}
4、创建AOP并引导加载配置类
避免限流处理侵入业务功能代码,通过借助Spring AOP,定义一个拦截器,注解@Aspect,同时创建spring.factories文件,引导SpringBoot加载配置类
5、在业务模块/项目中引入通用限流器模块
5.1、引入pom依赖
<dependency>
<groupId>com.busi</groupId>
<artifactId>common-limiter</artifactId>
</dependency>
5.2、在配置文件application.properties中设置使用的限流模式:limit.mod = remote
5.3、在需要限流的业务接口上增加注解
@Limit(key = "Limiter:app",limitNum = 5,seconds = 0.5)
小结
通过上述整合步骤,我们已成功实现了将一个通用的限流器模块集成到其他业务模块中,在实际场景里,用户只需要根据场景需求,通过配置切换对应的限流模式,兼具灵活性及可维护性。