当前位置: 首页 > 后端技术 > Java

分布式限流redissionRRateLimiter的使用和原理

时间:2023-04-01 19:46:04 Java

Premise最近公司在做分布式限流,有需求的话。研究的限流框架大概包括1.springcloudgateway集成了redis限流,但是属于网关层限流2.阿里哨兵,功能强大,自带监控平台3.srpingcloudhystrix,属于接口层限流,提供线程池和信号量两种方法4.其他:redission和手动代码的实际需求属于业务端限流,Redission使用起来更方便,更灵活。下面介绍redission分布式限流的使用方法和原理:1.使用非常简单,如下//1.声明一个限流器RRateLimiterrateLimiter=redissonClient.getRateLimiter(key);//2.设置速率并在5秒内生成3个令牌rateLimiter.trySetRate(RateType.OVERALL,3,5,RateIntervalUnit.SECONDS);//3.尝试获取token,返回truerateLimiter.tryAcquire(1)二、原理1.getRateLimiter//声明一个限流器,名字为keyredissonClient.getRateLimiter(key)2.trySetRatetrySetRate方法在后面,底层实现如下如下:@OverridepublicRFuturetrySetRateAsync(RateTypetype,longrate,longrateInterval,RateIntervalUnitunit){returncommandExecutor.evalWriteAsync(getName(),LongCodec.INSTANCE,RedisCommands.EVAL_BOOLEAN,"redis.call('hsetnx',KEYS[1],'rate',ARGV[1]);"+"redis.call('hsetnx',KEYS[1],'interval',ARGV[2]);"+"returnredis.call('hsetnx',KEYS[1],'type',ARGV[3]);",Collections.singletonList(getName()),rate,unit.toMillis(rateInterval),类型.单个实例))trySetRate(RateType.OVERALL,3,5,RateIntervalUnit.SECONDS);然后redis中会设置3个参数:hsetnx,key,rate,3hsetnx,key,interval,5hsetnx,key,type,0然后看tryAcquire(1)方法:底层源码如下privateRFuturetryAcquireAsync(RedisCommandcommand,Longvalue){returncommandExecutor.evalWriteAsync(getName(),LongCodec.INSTANCE,command,"localrate=redis.call('hget',KEYS[1],'rate');"//1+"localinterval=redis.call('hget',KEYS[1],'interval');"//2+"localtype=redis.call('hget',KEYS[1],'type');"//3+"assert(rate~=falseandinterval~=falseandtype~=false,'RateLimiterisnotinitialized')"//4+"localvalueName=KEYS[2];"//5+"iftype==1then"+"valueName=KEYS[3];"//6+"end;"+"localcurrentValue=redis.call('get',valueName);"//7+"ifcurrentValue~=falsethen"+"iftonumber(currentValue)asList(getName(),getValueName(),getClientValueName()),value,commandExecutor.getConnectionManager().getId().toString());}第一,第二,第三remark行是获取上一步设置的3个值:rate,interval,type,如果没有设置这3个值,会直接返回rateLimiter,还没有初始化。第5行remark声明了一个变量名为valueName,值为KEYS[2],KEYS[2]对应的值为getValueName()方法,getValueName()返回我们在上面第一步getRateLimiter中设置的key;如果type=1,则表示全局sharing,则valueName的值改为KEYS[3],KEYS[3]对应的值为getClientValueName(),查看getClientValueName()源码:StringgetClientValueName(){returnsuffixName(getValueName(),commandExecutor.getConnectionManager().getId().toString());}ConnectionManager().getId()如下:publicinterfaceConnectionManager{UUIDgetId();omitted...}这个getId()就是每个client被初始化,即每个client的getId是唯一的,这也验证了trySetRate方法RateType.ALL和RateType.PER_CLIENT的作用。然后查看第7行获取valueName对应的值currentValue;第一次获取一定是空的,然后看第10行else逻辑setvalueName3px5,setkey=valueNamevalue=3expirationtimeis5secondsdecrbyvalueName1,上面的valueName减1,那么如果第二次访问第7条标记行返回的值存在,会转到第8条标记行,如果当前valueName为3,小于要获取的值的token个数,则进行下面的判断(tryAcquire方法中的入参),则表示当前时间内(key有效期5秒内)token数量用完,返回pttl(key剩余过期时间);否则,表示有足够的??令牌获得,则桶中的令牌数减1,至此结束。综上所述,redission分布式限流采用了令牌桶和固定时间窗口的思想。trySetRate方法设置bucket的大小,利用redis的key过期机制达到时间窗的目的,控制固定时间窗内允许通过的请求数。