分布式限流实战--redis实现令牌桶限流
这篇文章我们主要是分析一下分布式限流的玩法。 因为限流也是一个经典用法了。1.微服务限流随着微服务的流行,服务和服务之间的稳定性变得越来越重要。缓存、降级和限流是保护微服务系统运行稳定性的三大利器。缓存的目的是提升系统访问速度和增大系统能处理的容量,而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开,而有些场景并不能用缓存和降级来解决,比如稀缺资源、数据库的写
这篇文章我们主要是分析一下分布式限流的玩法。 因为限流也是一个经典用法了。
1.微服务限流
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。缓存、降级和限流是保护微服务系统运行稳定性的三大利器。
缓存的目的是提升系统访问速度和增大系统能处理的容量,而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开,而有些场景并不能用缓存和降级来解决,比如稀缺资源、数据库的写操作、频繁的复杂查询,因此需有一种手段来限制这些场景的请求量,即限流。
比如当我们设计了一个函数,准备上线,这时候这个函数会消耗一些资源,处理上限是1秒服务3000个QPS,但如果实际情况遇到高于3000的QPS该如何解决呢?
所以限流的目的应当是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率就可以拒绝服务、等待、降级。
学习如何去实现一个分布式限流框架,首先,我们需要去了解最基本的两种限流算法。
2.限流算法
2.1漏桶算法
漏桶算法思路很简单,水(也就是请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。示意图(来源网络)如下:
微服务架构下的分布式限流方案全解析
2.2令牌桶算法
令牌桶算法和漏桶算法效果一样但方向相反的算法,更加容易理解。随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入令牌(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个令牌,如果没有令牌可拿了就阻塞或者拒绝服务。示意图(来源网络)如下:
微服务架构下的分布式限流方案全解析
2.3算法选择
漏桶算法与令牌桶算法的区别在于,漏桶算法能够强行限制数据的传输速率,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发情况。令牌桶还有一个好处是可以方便的改变速度。一旦需要提高速率,则按需提高放入桶中的令牌的速率。所以,限流框架的核心算法还是以令牌桶算法为主。
3.本地限流
已知上面讲述的令牌桶算法的原理,如何通过代码实现?
本地限流的实现可以用Long长整型作为令牌桶,为了达到无锁,建议使用Long的原子类型AtomicLong,使用AtomicLong的好处就是可以非常方便的对其进行CAS加操作与CAS减操作(也就是令牌桶令牌的放入与拿取),以避免线程的上下文切换的开销,核心CAS算法如下:
private boolean tryAcquireFailed() {
long l = bucket.longValue();
while (l > 0) {
if (bucket.compareAndSet(l, l - 1)) {
return true;
}
l = bucket.longValue();
}
return false;
}
根据上述了解的令牌桶算法可以得知,令牌桶需要一个ScheduledThread不断的放入令牌,这部分的代码如下:
ScheduledThreadExecutor.scheduleAtFixedRate(() ->
bucket.set(rule.getLimit()), rule.getInitialDelay(), rule.getPeriod(), rule.getUnit()
);
4.分布式限流概述
分布式限流需要解决什么问题呢?我想至少有下面几个:
1.动态规则:比如限流的QPS我们希望可以动态修改,限流的功能可以随时开启、关闭,限流的规则可以跟随业务进行动态变更等。
2.集群限流:比如对Spring Cloud微服务架构中的某服务下的所有实例进行统一限流,以控制后续访问数据库的流量。
3.熔断降级:比如在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
可选的其它几个功能,诸如实时监控数据、网关流控、热点参数限流、系统自适应限流、黑白名单控制、注解支持等,这些功能其实可以非常方便的进行扩展。
以上的内容来源于 分布式限流概念 ,大家可以查看。
分布式限流实操–令牌桶算法限流
这里就直接帖一下代码就好了
/**
* 令牌桶算法限流
* @author 田培融
*/
@Slf4j
@Component
public class RedisRaterLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
public String acquireToken(String point, Integer limit, Long timeout) {
String maxCountKey = "BUCKET:MAX_COUNT:" + point;
String currCountKey = "BUCKET:CURR_COUNT:" + point;
try {
// 令牌值
String token = "T";
// 无效的限流值 返回token
if(limit<=0||timeout<=0){
return token;
}
// maxCount为主要判断标志
String maxCount = redisTemplate.opsForValue().get(maxCountKey);
String currCount = redisTemplate.opsForValue().get(currCountKey);
// 初始
if(StrUtil.isBlank(maxCount)){
// 初始计数为1
redisTemplate.opsForValue().set(currCountKey, "1", timeout, TimeUnit.MILLISECONDS);
// 总数
redisTemplate.opsForValue().set(maxCountKey, limit.toString(), timeout, TimeUnit.MILLISECONDS);
return token;
} else if (StrUtil.isNotBlank(maxCount)&&StrUtil.isNotBlank(currCount)){
// 判断是否超过限制
if(Integer.valueOf(currCount)<Integer.valueOf(maxCount)){
// 计数加1
redisTemplate.opsForValue().set(currCountKey, String.valueOf(Integer.valueOf(currCount)+1), timeout, TimeUnit.MILLISECONDS);
return token;
}
} else {
// currCount变量先失效(几乎不可能) 返回token
return token;
}
} catch (Exception e) {
log.error("限流出错,请检查Redis运行状态\n"+e.toString());
}
return null;
}
}
第一个参数只要不重复就行,随便写。 主要是为了区别要限制什么。 第二个参数是表示限制访问的次数。第三个参数是表示多长时间内限制访问的次数。
使用案例:
String token = redisRaterLimiter.acquireToken("upload:"+ipInfoUtil.getIpAddr(request), 1, 300000L);
if (StrUtil.isBlank(token)) {
throw new LimitException("上传那么多干嘛,等等再传吧");
}
好了,这就是我们项目中的分布式限流了。
猿来衣舍
这是博主开的淘宝小店,主要经营舒适保暖的服饰,有袜子、主题卫衣、保暖衣。欢迎大家选购。
TEngine java开发脚手架
测试账号 test 123456
我这个脚手架目前是做为店铺福利送给顾客的,联系客户获取
打开淘宝搜索 “猿来衣舍”这四个字就可以搜到小店,希望大家多多支持。
一个人能够走多远,关键在于与谁同行,我用跨越山海的一路相伴,希望得到您用金钱的称赞。
更多推荐
所有评论(0)