Springboot中怎么实现分布式限流
这期内容当中小编将会给大家带来有关Springboot中怎么实现分布式限流,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
限流算法介绍
a、令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝。
b、漏桶算法
其主要目的是控制数据注入到网络的速率,平滑网络上的突发流量,数据可以以任意速度流入到漏桶中。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。 漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶为空,则不需要流出水滴,如果漏桶(包缓存)溢出,那么水滴会被溢出丢弃
c、计算器限流
计数器限流算法是比较常用一种的限流方案也是最为粗暴直接的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、接口访问并发数等都是使用计数器算法
如:使用AomicInteger来进行统计当前正在并发执行的次数,如果超过域值就直接拒绝请求,提示系统繁忙
限流具体代码实践
a、导入依赖
org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis com.google.guava guava 21.0 org.apache.commons commons-lang3 org.springframework.boot spring-boot-starter-test
b、属性配置
在application.properites资源文件中添加redis相关的配置项
spring.redis.host=192.168.68.110 spring.redis.port=6379 spring.redis.password=123456
默认情况下spring-boot-data-redis为我们提供了StringRedisTemplate但是满足不了其它类型的转换,所以还是得自己去定义其它类型的模板
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
importorg.springframework.data.redis.core.RedisTemplate;
importorg.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
importorg.springframework.data.redis.serializer.StringRedisSerializer;
importjava.io.Serializable;
/**
*redis配置
*/
@Configuration
publicclassRedisConfig{
@Bean
publicRedisTemplatelimitRedisTemplate(LettuceConnectionFactoryredisConnectionFactory){
RedisTemplatetemplate=newRedisTemplate<>();
template.setKeySerializer(newStringRedisSerializer());
template.setValueSerializer(newGenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
returntemplate;
}
} d、Limit 注解
具体代码如下
importcom.carry.enums.LimitType;
importjava.lang.annotation.Documented;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Inherited;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
/**
*限流
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public@interfaceLimit{
/**
*资源的名字
*
*@returnString
*/
Stringname()default"";
/**
*资源的key
*
*@returnString
*/
Stringkey()default"";
/**
*Key的prefix
*
*@returnString
*/
Stringprefix()default"";
/**
*给定的时间段
*单位秒
*
*@returnint
*/
intperiod();
/**
*最多的访问限制次数
*
*@returnint
*/
intcount();
/**
*类型
*
*@returnLimitType
*/
LimitTypelimitType()defaultLimitType.CUSTOMER;
}packagecom.carry.enums;
publicenumLimitType{
/**
*自定义key
*/
CUSTOMER,
/**
*根据请求者IP
*/
IP;
}e、Limit 拦截器(AOP)
我们可以通过编写 Lua 脚本实现自己的API,核心就是调用execute方法传入我们的 Lua 脚本内容,然后通过返回值判断是否超出我们预期的范围,超出则给出错误提示。
importcom.carry.annotation.Limit;
importcom.carry.enums.LimitType;
importcom.google.common.collect.ImmutableList;
importorg.apache.commons.lang3.StringUtils;
importorg.aspectj.lang.ProceedingJoinPoint;
importorg.aspectj.lang.annotation.Around;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.reflect.MethodSignature;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.data.redis.core.RedisTemplate;
importorg.springframework.data.redis.core.script.DefaultRedisScript;
importorg.springframework.data.redis.core.script.RedisScript;
importorg.springframework.web.context.request.RequestContextHolder;
importorg.springframework.web.context.request.ServletRequestAttributes;
importjavax.servlet.http.HttpServletRequest;
importjava.io.Serializable;
importjava.lang.reflect.Method;
@Aspect
@Configuration
publicclassLimitInterceptor{
privatestaticfinalLoggerlogger=LoggerFactory.getLogger(LimitInterceptor.class);
privatefinalRedisTemplatelimitRedisTemplate;
@Autowired
publicLimitInterceptor(RedisTemplatelimitRedisTemplate){
this.limitRedisTemplate=limitRedisTemplate;
}
@Around("execution(public**(..))&&@annotation(com.carry.annotation.Limit)")
publicObjectinterceptor(ProceedingJoinPointpjp){
MethodSignaturesignature=(MethodSignature)pjp.getSignature();
Methodmethod=signature.getMethod();
LimitlimitAnnotation=method.getAnnotation(Limit.class);
LimitTypelimitType=limitAnnotation.limitType();
Stringname=limitAnnotation.name();
Stringkey;
intlimitPeriod=limitAnnotation.period();
intlimitCount=limitAnnotation.count();
switch(limitType){
caseIP:
key=getIpAddress();
break;
caseCUSTOMER:
key=limitAnnotation.key();
break;
default:
key=StringUtils.upperCase(method.getName());
}
ImmutableListkeys=ImmutableList.of(StringUtils.join(limitAnnotation.prefix(),key));
try{
StringluaScript=buildLuaScript();
RedisScriptredisScript=newDefaultRedisScript<>(luaScript,Number.class);
Numbercount=limitRedisTemplate.execute(redisScript,keys,limitCount,limitPeriod);
logger.info("Accesstrycountis{}forname={}andkey={}",count,name,key);
if(count!=null&&count.intValue()<=limitCount){
returnpjp.proceed();
}else{
thrownewRuntimeException("Youhavebeendraggedintotheblacklist");
}
}catch(Throwablee){
if(einstanceofRuntimeException){
thrownewRuntimeException(e.getLocalizedMessage());
}
thrownewRuntimeException("serverexception");
}
}
/**
*限流脚本
*
*@returnlua脚本
*/
publicStringbuildLuaScript(){
StringBuilderlua=newStringBuilder();
lua.append("localc");
lua.append("\nc=redis.call('get',KEYS[1])");
//调用不超过最大值,则直接返回
lua.append("\nifcandtonumber(c)>tonumber(ARGV[1])then");
lua.append("\nreturnc;");
lua.append("\nend");
//执行计算器自加
lua.append("\nc=redis.call('incr',KEYS[1])");
lua.append("\niftonumber(c)==1then");
//从第一次调用开始限流,设置对应键值的过期
lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
lua.append("\nend");
lua.append("\nreturnc;");
returnlua.toString();
}
privatestaticfinalStringUNKNOWN="unknown";
/**
*获取IP地址
*@return
*/
publicStringgetIpAddress(){
HttpServletRequestrequest=((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
Stringip=request.getHeader("x-forwarded-for");
if(ip==null||ip.length()==0||UNKNOWN.equalsIgnoreCase(ip)){
ip=request.getHeader("Proxy-Client-IP");
}
if(ip==null||ip.length()==0||UNKNOWN.equalsIgnoreCase(ip)){
ip=request.getHeader("WL-Proxy-Client-IP");
}
if(ip==null||ip.length()==0||UNKNOWN.equalsIgnoreCase(ip)){
ip=request.getRemoteAddr();
}
returnip;
}
} f、控制层
在接口上添加@Limit()注解,如下代码会在 Redis 中生成过期时间为 100s 的 key = test 的记录,特意定义了一个AtomicInteger用作测试
importcom.carry.annotation.Limit;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RestController;
importjava.util.concurrent.atomic.AtomicInteger;
@RestController
publicclassLimiterController{
privatestaticfinalAtomicIntegerATOMIC_INTEGER=newAtomicInteger();
@Limit(key="test",period=100,count=10,name="resource",prefix="limit")
@GetMapping("/test")
publicinttestLimiter(){
//意味着100S内最多可以访问10次
returnATOMIC_INTEGER.incrementAndGet();
}
}注意:上面例子保存在redis中的key值应该为“limittest”,即@Limit中prefix的值+key的值
测试
我们在postman中快速访问localhost:8080/test,当访问数超过10时出现以下结果
上述就是小编为大家分享的Springboot中怎么实现分布式限流了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注恰卡编程网行业资讯频道。
推荐阅读
-
vue动态添加删除输入框(springboot vue怎么让数据库显示出来)
springbootvue怎么让数据库显示出来?一般情况下是前端调阅后端接口,来获取到数据库的数据,后端哪里会把数据库的数据整理...
-
springboot实现基于aop的切面日志
本文实例为大家分享了springboot实现基于aop的切面日志的具体代码,供大家参考,具体内容如下通过aop的切面方式实现日志...
-
SpringBoot定时任务功能怎么实现
-
SpringBoot中的@Import注解怎么使用
-
SpringBoot整合Lombok及常见问题怎么解决
SpringBoot整合Lombok及常见问题怎么解决这篇文章主要...
-
springboot图片验证码功能模块怎么实现
springboot图片验证码功能模块怎么实现本篇内容主要讲解“s...
-
Springboot+SpringSecurity怎么实现图片验证码登录
-
SpringBoot注解的知识点有哪些
SpringBoot注解的知识点有哪些这篇“SpringBoot注...
-
SpringBoot2.x中management.security.enabled=false无效怎么解决
SpringBoot2.x中management.security.enabled=false无效怎么解决...
-
springboot怎么禁用某项健康检查
springboot怎么禁用某项健康检查今天小编给大家分享一下sp...
