Springboot中怎么实现分布式限流
这期内容当中小编将会给大家带来有关Springboot中怎么实现分布式限流,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
限流算法介绍
a、令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝。
b、漏桶算法
其主要目的是控制数据注入到网络的速率,平滑网络上的突发流量,数据可以以任意速度流入到漏桶中。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。 漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶为空,则不需要流出水滴,如果漏桶(包缓存)溢出,那么水滴会被溢出丢弃
c、计算器限流
计数器限流算法是比较常用一种的限流方案也是最为粗暴直接的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、接口访问并发数等都是使用计数器算法
如:使用AomicInteger
来进行统计当前正在并发执行的次数,如果超过域值就直接拒绝请求,提示系统繁忙
限流具体代码实践
a、导入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>21.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies>
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 publicRedisTemplate<String,Serializable>limitRedisTemplate(LettuceConnectionFactoryredisConnectionFactory){ RedisTemplate<String,Serializable>template=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); privatefinalRedisTemplate<String,Serializable>limitRedisTemplate; @Autowired publicLimitInterceptor(RedisTemplate<String,Serializable>limitRedisTemplate){ 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()); } ImmutableList<String>keys=ImmutableList.of(StringUtils.join(limitAnnotation.prefix(),key)); try{ StringluaScript=buildLuaScript(); RedisScript<Number>redisScript=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图片验证码功能模块怎么实现
-
Springboot+SpringSecurity怎么实现图片验证码登录
-
SpringBoot注解的知识点有哪些
SpringBoot注解的知识点有哪些这篇“SpringBoot注...
-
SpringBoot2.x中management.security.enabled=false无效怎么解决
-
springboot怎么禁用某项健康检查
springboot怎么禁用某项健康检查今天小编给大家分享一下sp...