此项目是基于Spring Redis Lua为一个无侵入的应用级网关限流框架,如果您正在寻找一个网关限流的框架,使用spring-redis-current-limit是最明智的选择
- 无需任何复杂配置文件,一个注解玩转spring-redis-current-limit
- 细粒度控制,您可以控制同一个类中的A方法每分钟限流100而B方法每分钟限流200
- 高灵活性,可根据自定义信息(如用户id、用户ip、用户权限等)进行限流、可灵活选择限流算法
- 高可用性,使用redis+lua脚本的原子性为分布式系统保驾护航
- 高可扩展性,可灵活添加限流算法
<dependency>
<groupId>com.github.chqiuu</groupId>
<artifactId>spring-redis-current-limit</artifactId>
<version>Latest Version</version>
</dependency>
因为并不是所有的项目都会使用SpringBoot,所以在注册这一步我们分为两种情况
您需要在启动类上增加一个注解
@EnableCurrentLimit
@SpringBootApplication
public class StudyApplication {
public static void main(String[] args) {
SpringApplication.run(StudyApplication.class, args);
}
}
您需要提供一个可以被Spring管理的配置类。比如说:
@Import(EnableCurrentLimitConfiguration.class)
@Configuration
public class CurrentLimitConfig {
}
您需要配置您的redis连接为spring-redis-current-limit,同2的情况我们把项目分为两种情况(注意下方的配置需要根据实际情况调整)
spring:
redis:
host:
port:
password:
timeout: 2000
您只需要注册一个RedisConnectionFactory子类的bean。比如说
<beans>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" >
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="poolConfig" ref="poolConfig" />
<property name="port" value="${redis.port}" />
<property name="hostName" value="${redis.host}" />
<property name="timeout" value="${redis.timeout}" ></property>
<property name="database" value="1"></property>
</bean>
</beans>
其实看到这一步的时候您已经可以使用spring-redis-current-limit来进行限流了哦。
接下来介绍一下注解中的几个属性
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CurrentLimit {
/**
* 设置限流条件唯一标识,默认为系统自动生成。建议不配置此项,若想要配置请保证此项的唯一性
*
* @return 限流条件唯一标识
*/
String key() default "";
/**
* 设置提示消息
*
* @return 被限流时的提示消息
*/
String message() default "您的手速太快了,请稍后再试";
/**
* 设置给定的时间范围 单位(秒)
*
* @return 给定的时间范围 单位(秒)。默认值60
*/
long interval() default 60;
/**
* 设置单位时间内可访问次数(限流次数)
* 当为令牌桶算法时,为向令牌桶中添加数据的时间间隔, 以秒为单位。默认值10秒
*
* @return 单位时间内可访问次数(限流次数)。默认值10
*/
long limit() default 10;
/**
* 设置每次为令牌桶中添加的令牌数量
*
* @return 每次为令牌桶中添加的令牌数量。默认值5个
*/
long step() default 5;
/**
* 设置限流类型。默认值:LOCAL
*/
LimitTypeEnum limitType() default LimitTypeEnum.LOCAL;
/**
* 设置在类上设置注解限流模式,当为ElementType.TYPE时有效
*
* @return 在类上设置注解限流模式
*/
TypeLimitModelEnum typeLimitModel() default TypeLimitModelEnum.EACH;
}
来几个使用的例子吧
- 限流一个Controller中的所有接口。(例如,需要每个方法每5秒只允许调用3次)
@CurrentLimit(interval = 5, limit = 3, message = "在类上做限流,每个接口每5秒只能访问3次,您已经超过3次访问,请稍后再试")
@RestController
@RequestMapping("/type/each")
public class TypeEachCurrentLimitController {
private static final AtomicInteger ATOMIC_INTEGER_HAVE_PARAM = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_NO_PARAM = new AtomicInteger();
@GetMapping("/haveParam")
public String haveParam(String param) {
return String.format("第%s次访问【param:%s】,成功获得数据!", ATOMIC_INTEGER_HAVE_PARAM.incrementAndGet(), param);
}
@GetMapping("/noParam")
public String noParam() {
return String.format("第%s次访问,成功获得数据!", ATOMIC_INTEGER_NO_PARAM.incrementAndGet());
}
}
- 根据IP限流访问次数
//每个IP每20秒可以访问5次
@CurrentLimit(interval = 20, limit = 5, limitType = LimitTypeEnum.IP, message = "IP,您的手速太快了,请稍后再试")
- 限流某个方法的并发数
@RestController
@RequestMapping("/method")
public class MethodLimiterController {
private static final AtomicInteger ATOMIC_INTEGER_LOCAL = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_IP = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_USER = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_SESSION = new AtomicInteger();
private static final AtomicInteger ATOMIC_INTEGER_CUSTOM = new AtomicInteger();
@CurrentLimit(interval = 5, limit = 3, message = "此方法对所有来源的请求都限流,5秒只能访问3次,您已经超过3次访问,请稍后再试")
@GetMapping("/local")
public String local() {
return String.format("第%s次访问,成功获得数据!", ATOMIC_INTEGER_LOCAL.incrementAndGet());
}
@CurrentLimit(interval = 5, limit = 3
, message = "此方法访问根据IP限流,5秒只能访问3次,您已经超过3次访问,请稍后再试")
@GetMapping("/ip")
public String ip() {
return String.format("第%s次访问,成功获得数据!", ATOMIC_INTEGER_IP.incrementAndGet());
}
@CurrentLimit(interval = 5, limit = 3
, message = "此方法访问根据用户限流,5秒只能访问3次,您已经超过3次访问,请稍后再试")
@GetMapping("/user")
public String user() {
return String.format("第%s次访问,成功获得数据!", ATOMIC_INTEGER_USER.incrementAndGet());
}
@CurrentLimit(interval = 5, limit = 3
, message = "此方法访问根据Session限流,5秒只能访问3次,您已经超过3次访问,请稍后再试")
@GetMapping("/session")
public String session() {
return String.format("第%s次访问,成功获得数据!", ATOMIC_INTEGER_SESSION.incrementAndGet());
}
@CurrentLimit(interval = 5, limit = 3, limitType = LimitTypeEnum.CUSTOM
, message = "此方法为自定义限流,5秒只能访问3次,您已经超过3次访问,请稍后再试")
@GetMapping("/custom")
public String testLimiter2(HttpServletRequest request) {
//根据一系列操作查出来了用户id
//限流时在httpServletRequest中根据Const.CUSTOM的值进行限流
request.setAttribute(Constant.CUSTOM, "user_id");
return String.format("第%s次访问,成功获得数据!", ATOMIC_INTEGER_CUSTOM.incrementAndGet());
}
}
- 更多示例请移步演示示例
相信看完了上方的Quick Start您已经迫不及待的想要将spring-redis-current-limit应用于生产了。我在这里为您提供了两种限流算法。您可以根据自己系统的需求选择自己需要的算法
如果您对限流算法不太了解的话可以先参考一下这篇文章高并发系统的限流方案研究,其实限流实现也不复杂
- 令牌桶算法
程序默认使用令牌桶算法进行限流,如果您要使用令牌桶算法的话无需要额外的配置。 - 计数器算法
如果您想要使用计数器算法的话,只需要增加一个配置即可。在配置文件中指定算法为计数器算法。(推荐您使用yml文件)- yml配置方式
current-limit: algorithm: counter
- properties配置方式
current-limit.algorithm = counter
如果您想使用别的算法,您可以fork项目进行开发
将JAR包发布到Maven**仓库 在Maven**存储库中发布github项目
spring-redis-current-limit 正式上线