Springboot中怎么实现分布式限流

这期内容当中小编将会给大家带来有关Springboot中怎么实现分布式限流,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

限流算法介绍

a、令牌桶算法

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝。

Springboot中怎么实现分布式限流

b、漏桶算法

其主要目的是控制数据注入到网络的速率,平滑网络上的突发流量,数据可以以任意速度流入到漏桶中。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。 漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶为空,则不需要流出水滴,如果漏桶(包缓存)溢出,那么水滴会被溢出丢弃

Springboot中怎么实现分布式限流

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中怎么实现分布式限流

上述就是小编为大家分享的Springboot中怎么实现分布式限流了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注恰卡编程网行业资讯频道。

发布于 2021-06-13 23:20:46
收藏
分享
海报
0 条评论
183
上一篇:JAVA中HASHMAP怎么实现死循环 下一篇:Mybatis中resultMap如何使用
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码