TL;DR(太长勿读)限流算法:计数器,滑动窗口,漏桶,令牌桶。限流解决方案:Guava的RateLimiter和AlibabaSentinel是众所周知的。对于高并发的业务场景,为了保证服务的稳定性,我们往往会诉诸三个强大的工具:缓存、断路器降级、服务限流。业务限流作为一种核心的自我保护机制,可以在并发量非常高,其他机制无法保证降级的情况下,保护系统不至于崩溃,从而避免雪崩效应。我们想象这样一个场景。名词分析,QPS(querypersecond),单台机器最高能承受的QPS是100,我们有5台机器,每天的服务QPS是300。所以其实我们没有压力,根据前面的负载均衡服务器,每台300/5=60。可以完美的提供服务。今天老大突然搞了一波促销活动,QPS达到了800。嗯,机器A的QPS达到了160,完全受不了,直接崩溃了。此时集群只剩下4台机器,QPS还是800。平均分配给剩下的4台机器,每台机器200。就这样,机器一个接一个倒下,雪崩崩塌。那么如果我们的系统限流的话,会是一个什么样的场景呢?QPS已经达到800了,嗯,机器A的QPS已经达到了160,但是因为限流100,所以机器还在正常运行,但是60个QPS的客户就丢失了,整个集群还在正常运行。这个时候给开发者和运维人员留点时间,开始balabala的降级扩容……可见限流对于系统的自我保护非常重要,但是很多工程师没有重视,或者只是使用它,并不知道它背后的原理。先说结论吧。常见的限流算法包括:计数器、滑动窗口、漏桶和令牌桶。常见的限流方案有:Guava的RateLimiter、基于分布式锁的令牌桶、AlibabaSentinel一般而言,计数器比较粗糙,取决于单位时间内接受多少QPS请求。阈值,直接拒绝服务。场景大概是这样的。有这样一个煎饼果子摊,摊主是老王,上面老板说每分钟只能卖6个煎饼(每分钟限卖6个煎饼)。如果前0.1秒已经有人点了6个煎饼,而老王刚拿完神笔,那么老王只能在接下来的59.59秒坐在凳子上,等待下一分钟的到来。你看,一个简单粗暴的计数器,在系统性能允许的情况下,可能会浪费很多资源。滑动窗口可以看作是计数器的一种细化实现。以前只能一分一秒的向前推进,现在却可以根据实现的细化,一秒一秒的向前推进,虽然整体的原理还是要看计数器。放下过去,是懂得适时忘记的计数器。看这张图,就可以看出leakybucket的基本原理了。我们会用一个桶作为缓冲区,所有的请求都会先丢到桶里。系统以恒定的速度慢慢消化这些请求。比较常见的实现是队列,用一个缓冲区来存放未处理的请求,然后消费者以恒定的速度抓取一些请求进行处理。有这么一家煎饼水果摊,店主是老王,每秒只能做一个煎饼。现在有100个客户,我该怎么办?排队就行了老王的老婆潘某带领这群顾客站在旁边的空地上,一一标上号码。老王做好了一个,喊出号码,对应的顾客就过来把蛋糕拿走了。如果看这里的需求,有空地(bucket),客户可以等得起(waittime)。<令牌桶>我们会有一个令牌管理员,按照一定的策略把令牌放入令牌桶中。系统每次收到请求,都会请求一个token。拿到token就处理请求,没拿到就拒绝请求。所以只要代币发行策略正确,系统就不会被拖累,机器的利用率也会更高。有这么一家煎饼果子摊,摊主是老王,老王不知道自己能做多少个煎饼。老王的妻子阿盘在王老王身边放了一个水桶,里面放了一些牌子,对王老王说:“我帮你看,看到信物就去做。”现在有100个客户,老王挖粪刷墙,一秒只能做一个,现在一秒可以做不止一个,老王不看客户了,他自己做直接每次都能拿到令牌。老王的妻子潘一直看着老王,看他的手是不是在发抖,正要上厕所。如果你的手在颤抖,或者你可能坚持不住了,那就少放一些代币,休息一下。可如果一次来了五位VIP客户,阿盼也顾不得那么多了,多扔几个信物让老王忙个不停。我们已经看到,令牌桶方式可以根据系统负载实时调整系统的处理能力,可以快速消化一定程度的瞬时高峰流量。好的。方案和算法基本完成。下面说说目前限流的实现方式。当然,我们真的希望不用太多开发就可以开箱即用。幸运的是,我们已经有很多开源了,即使你自己做也不会特别困难。com.google.guavaguava25.1-jre使用Guava的RateLimiter进行限流控制,主要有有两种核心模式,SmoothBursty和SmoothWarmingUp。SmoothBursty每秒发行N个代币,也允许预借一定数量的代币。SmoothWarmingUp,在系统刚启动时,只会在最低阈值时发放代币,然后逐渐增加到设定的最高阈值。RateLimitersmoothBuisty=RateLimiter.create(1);RateLimitersmoothWarmingUp=RateLimiter.create(1,1,TimeUnit.SECONDS);smoothBuisty.acquire();smoothWarmingUp.acquire(5);acquire()方法会阻塞,直到令牌桶返回,你也可以一次获取N个令牌。但RateLimiter是一个独立版本。如果我们要实现分布式,可以基于RateLimiter的原理实现如下分布式,可以使用Redis等分布式锁来实现。https://github.com/alibaba/Sentinel.gitSentinel是一个带有配置中心的分布式缓存,以“资源名”为统计点,提供多种限流方案,可以基于QPS,线程数甚至系统负载,都是用来做集群规模限流的。Sentinel在整个生态中的地位是这样的。使用限流的代码非常简单。你只需要定义一个String类型的资源作为唯一标识,Sentinel会根据规则进行限流。try(Entryentry=SphU.entry("HelloWorld")){//Yourbusinesslogichere.System.out.println("helloworld");}catch(BlockExceptione){//Handlerejectedrequest.e.printStackTrace();}定义限流规则代码很简单,只需要定义一个String类型的资源,作为唯一标识,Sentinel会根据规则进行限流。privatestaticvoidinitFlowRules(){Listrules=newArrayList<>();FlowRulerule=newFlowRule();rule.setResource("HelloWorld");rule.setGrade(RuleConstant.FLOW_GRADE_QPS);//设置limitQPSto20.rule.setCount(20);rules.add(rule);FlowRuleManager.loadRules(rules);}同时提供DashBoard,用于实时调整规则。最后总结一下今天关于限流算法的总结:计数器、滑动窗口、漏桶、令牌桶。限流方案:Guava的RateLimiter、基于分布式锁的tokenbucket、AlibabaSentinel