随着微服务的流行,服务之间的稳定性变得越发重要,往往我们会花很多经历在维护服务的稳定性上,限流和熔断降级是我们最常用的两个手段。前段时间在群里有些小伙伴对限流的使用些疑问,再加上最近公司大促也做了限流相关的事,所以在这里总结一下写写自己对限流的一些看法。
刚才说了限流是我们保证服务稳定性的手段之一,但是他并不是所有场景的稳定性都能保证,和他名字一样他只能在大流量或者突发流量的场景下才能发挥出自己的作用。比如我们的系统最高支持100QPS,但是突然有1000QPS请求打了进来,可能这个时候系统就会直接挂掉,导致后面一个请求都处理不了,但是如果我们有限流的手段,无论他有多大的QPS,我们都只处理100QPS的请求,其他请求都直接拒绝掉,虽然有900的QPS的请求我们拒绝掉了,但是我们的系统没有挂掉,我们系统仍然可以不断的处理后续的请求,这个是我们所期望的。有同学可能会说,现在都上的云了,服务的动态伸缩应该是特别简单的吧,如果我们发现流量特别大的时候,自动扩容机器到可以支撑目标QPS那不就不需要限流了吗?其实有这个想法的同学应该还挺多的,有些同学可能被一些吹牛的文章给唬到了,所以才会这么想,这个想法在特别理想化的时候是可以实现的,但是在现实中其实有下面几个问题:
所以单纯的扩容是解决不了这个问题的,限流仍然是我们必须掌握的技能!Spring全家桶也是必备的宝典之一!
想要掌握好限流,就需要先掌握他的一些基本算法,限流的算法基本上分为三种,计数器,漏斗,令牌桶,其他的一些都是在这些基础上进行演变而来。
首先我们来说一下计数器算法,这个算法比较简单粗暴,我们只需要一个累加变量,然后每隔一秒钟去刷新这个累加变量,然后再判断这个累加变量是否大于我们的最大QPS。
int curQps = 0;
long lastTime = System.currentTimeMillis();
int maxQps = 100;
Object lock = new Object();
boolean check(){
synchronized (lock){
long now = System.currentTimeMillis();
if (now - lastTime > 1000){
lastTime = now;
curQps = 0;
}
curQps++;
if (curQps > maxQps){
return false;
}
}
return true;
}
这个代码比较简单,我们定义了当前的qps,以及上一次刷新累加变量的时间,还有我们的最大qps和我们的lock锁,我们每次检查的时候,都需要判断是否需要刷新,如果需要刷新那么需要把时间和qps都进行重置,然后再进行qps的累加判断。
这个算法因为太简单了所以带来的问题也是特别明显,如果我们最大的qps是100,在0.99秒的时候来了100个请求,然后在1.01秒的时候又来了100个请求,这个是可以通过我们的程序的,但是我们其实在0.03秒之内通过了200个请求,这个肯定不符合我们的预期,因为很有可能这200个请求直接就会将我们机器给打挂。
为了解决上面的临界的问题,我们这里可以使用滑动窗口来解决这个问题:
如上图所示,我们将1s的普通计数器,分成了5个200ms,我们统计的当前qps都需要统计最近的5个窗口的所有qps,再回到刚才的问题,0.99秒和1.01秒其实都在我们的最近5个窗口之内,所以这里不会出现刚才的临界的突刺问题。
其实换个角度想,我们普通的计数器其实就是窗口数量为1的滑动窗口计数器,只要我们分的窗口越多,我们使用计数器方案的时候统计就会越精确,但是相对来说维护的窗口的成本就会增加,等会我们介绍sentinel的时候会详细介绍他是怎么实现滑动窗口计数的。
解决计数器中临界的突刺问题也可以通过漏斗算法来实现,如下图所示:
在漏斗算法中我们需要关注漏桶和匀速流出,不论流量有多大都会先到漏桶中,然后以均匀的速度流出。如何在代码中实现这个匀速呢?比如我们想让匀速为100q/s,那么我们可以得到每流出一个流量需要消耗10ms,类似一个队列,每隔10ms从队列头部取出流量进行放行,而我们的队列也就是漏桶,当流量大于队列的长度的时候,我们就可以拒绝超出的部分。
漏斗算法同样的也有一定的缺点:无法应对突发流量(和上面的临界突刺不一样,不要混淆)。比如一瞬间来了100个请求,在漏桶算法中只能一个一个的过去,当最后一个请求流出的时候时间已经过了一秒了,所以漏斗算法比较适合请求到达比较均匀,需要严格控制请求速率的场景。
为了解决突发流量情况,我们可以使用令牌桶算法,如下图所示:
上面我们已经介绍了限流的一些基本算法,我们把这些算法应用到我们的分布式服务中又可以分为两种,一个是单机限流,一个是集群限流。单机限流指的是每台机器各自做自己的限流,互不影响。我们接下来看看单机限流怎么去实现呢?
guava是谷歌开源的java核心工具库,里面包括集合,缓存,并发等好用的工具,当然也提供了我们这里所需要的的限流的工具,核心类就是RateLimiter。
// RateLimiter rateLimiter = RateLimiter.create(100, 500, TimeUnit.MILLISECONDS); 预热的rateLimit
RateLimiter rateLimiter = RateLimiter.create(100); // 简单的rateLimit
boolean limitResult = rateLimiter.tryAcquire();
使用方式比较简单,如上面代码所示,我们只需要构建一个RateLimiter,然后再调用tryAcquire方法,如果返回为true代表我们此时流量通过,相反则被限流。在guava中RateLimiter也分为两种,一个是普通的令牌桶算法的实现,还有一个是带有预热的RateLimiter,可以让我们令牌桶的释放速度逐步增加直到最大,这个带有预热的在sentinel也有,这个可以在一些冷系统中比如数据库连接池没有完全填满,还在不断初始化的场景下使用。
在这里只简单的介绍一下guava的令牌桶怎么去实现的:
普通的令牌桶创建了一个SmoothBursty的类,这个类也就是我们实现限流的关键,具体怎么做限流的在我们的tryAcquire中:
这里分为四步:
扣除令牌的方法具体在reserverEarliestAvailable方法中:
这里虽然看起来过程比较多,但是如果我们只是调用tryAcquire(),就只需要关注两个红框:
guava的限流目前就提供了这两种方式的限流,很多中间件或者业务服务都把guava的限流作为自己的工具,但是guava的方式比较局限,动态改变限流,以及更多策略的限流都不支持,所以我们接下来介绍一下sentinel。
sentinel是阿里巴巴开源的分布式服务框架的轻量级流量控制框架,承接了阿里巴巴近 10 年的双十一大促流量的核心场景,他的核心是流量控制但是不局限于流量控制,还支持熔断降级,监控等等。
使用sentinel的限流稍微比guava复杂很多,下面写了一个最简单的代码:
String KEY = "test";
// ============== 初始化规则 =========
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(KEY);
// set limit qps to 20
rule1.setCount(20);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
rule1.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
FlowRuleManager.loadRules(rules);
// ================ 限流判定 ===========
Entry entry = null;
try {
entry = SphU.entry(KEY);
// do something
} catch (BlockException e1) {
// 限流会抛出BlockException 异常
}finally {
if (entry != null) {
entry.exit();
}
}
虽然sentinel的使用整体比guava复杂很多,但是算法的可选比guava的限流也多一点。
我们之前介绍的都是基于QPS的,在sentinel中提供了基于并发数的策略,效果类似于信号量隔离,当我们需要让业务线程池不被慢调用耗尽,我们就可以使用这种模式。
通常来说我们同一个服务提供的http接口都是使用的一个线程池,比如我们使用的tomcat-web服务器那么我们就会有个tomcat的业务线程池,如果在http中有两个方法A和B,B的速度相对来说比较快,A的速度相对来说比较慢,如果大量的调用A这个方法,由于A的速度太慢,线程得不到释放,有可能导致线程池被耗尽,另一个方法B就得不到线程。这个场景我们之前有遇到过直接导致整个服务所接收的请求全部被拒绝。有的同学说限制A的QPS不是就可以了吗,要注意的是QPS是每秒的,如果我们这个A接口的耗时大于1s,那么下一波A来了之后QPS是要重新计算的。
基于这个就提供了基于并发数的限流,我们设置Grade为FLOW_GRADE_THREAD,就可以实现这个限流模式。
基于QPS的限流sentinel也提供了4种策略:
sentinel提供了更为复杂的一种限流,可以基于调用关系去做更为灵活的限流:
sentinel虽然提供了这么多算法,但是也有一些问题:
这些问题基本上都是和guava的限流来比较的,毕竟sentinel的功能更多,付出的成本相对来说也会更多。
之前说的所有限流都是单机限流,但是我们现在都是微服务集群的架构模式,通常一个服务会有多台机器,比如有一个订单服务,这个服务有10台机器,那么我们想做整个集群限流到500QPS,我们应该怎么去做呢?这个很简单,直接每台机器都限流50就好了,50*10就是500,但是在现实环境中会出现负载不均衡的情况,在微服务调用的时候负载均衡的算法多种多样,比如同机房优先,轮训,随机等算法,这些算法都有可能导致我们的负载不是特别的均衡,就会导致我们整个集群的QPS可能有没有500,甚至在400的时候就被限流了,这个是我们真实场景中所遇到过的。既然单机限流有问题,那么我们应该设计一个更加完善的集群限流的方案
这个方案不依赖限流的框架,我们整个集群使用同一个redis即可,需要自己封装一下限流的逻辑,这里我们使用最简单的计数器去设计,我们将我们的系统时间以秒为单位作为key,设置到redis里面(可以设置一定的过期时间用于空间清理),利用redis的int原子加法,每来一个请求都进行+1,然后再判断当前值是否超过我们限流的最大值。
redis的方案实现起来整体来说比较简单,但是强依赖我们的系统时间,如果不同机器之间的系统时间有偏差限流就有可能不准确。
在sentinel中提供了集群的解决方案,这个对比其他的一些限流框架是比较有特色的。在sentinel中提供了两种模式:
当然sentinel也有一些兜底的策略,如果token-server挂了我们可以退化成我们单机限流的模式,不会影响我们正常的服务。
我们上面已经介绍了很多限流的工具,但是很多同学对怎么去限流仍然比较迷惑。我们如果对一个场景或者一个资源做限流的话有下面几个点需要确认一下:
这个问题比较复杂,很多公司以及很多团队的做法都不相同,在美团的时候搞了一波SOA,那个时候我记得所有的服务所有的接口都需要做限流,叫每个团队去给接口评估一个合理的QPS上限,这样做理论上来说是对的,我们每个接口都应该给与一个上限,防止把整体系统拖垮,但是这样做的成本是非常之高的,所以大部分公司还是选择性的去做限流。
首先我们需要确定一些核心的接口,比如电商系统中的下单,支付,如果流量过大那么电商系统中的路径就有问题,比如像对账这种边缘的接口(不影响核心路径),我们可以不设置限流。
其次我们不一定只在接口层才做限流,很多时候我们直接在网关层把限流做了,防止流量进一步渗透到核心系统中。当然前端也能做限流,当前端捕获到限流的错误码之后,前端可以提示等待信息,这个其实也算是限流的一部分。其实当限流越在下游触发我们的资源的浪费就越大,因为在下游限流之前上游已经做了很多工作了,如果这时候触发限流那么之前的工作就会白费,如果涉及到一些回滚的工作还会加大我们的负担,所以对于限流来说应该是越上层触发越好。
限多少流这个问题大部分的时候可能就是一个历史经验值,我们可以通过日常的qps监控图,然后再在这个接触上加一点冗余的QPS可能这个就是我们的限流了。但是有一个场景需要注意,那就是大促(这里指的是电商系统里面的场景,其他系统类比流量较高的场景)的时候,我们系统的流量就会突增,再也不是我们日常的QPS了,这种情况下,往往需要我们在大促之前给我们系统进行全链路压测,压测出一个合理的上限,然后限流就基于这个上限去设置。
一般来说大一点的互联网公司都有自己的统一限流的工具这里直接采用就好。对于其他情况的话,如果没有集群限流或者熔断这些需求,我个人觉得选择RateLimter是一个比较不错的选择,应该其使用比较简单,基本没有学习成本,如果有其他的一些需求我个人觉得选择sentinel,至于hytrix的话我个人不推荐使用,因为这个已经不再维护了。
限流虽然只有两个字,但是真正要理解限流,做好限流,是一件非常不容易的事,对于我个人而已,欢迎大家讨论!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有