当前位置: 首页 > 科技观察

我们公司的“双11”限流计划,快进来抄作业吧!

时间:2023-03-17 11:03:30 科技观察

图片来自抱途网如果景区能容纳1万人,现在已经有3万人进入,人山人海。如果发生意外,景区可能不得不关闭,无法对外开放。这样做的后果就是大家都觉得体验很糟糕。限流的思路是在保证可用性的情况下,尽可能增加进入的人数,剩下的人在外面排队等候,保证里面的一万人可以正常玩。回到互联网,也是如此。比如某明星公布恋情后,访问量从平时的50万一下子涨到了500万。该系统最多可支持200万次访问。那么就需要实现限流规则,保证它处于可用状态,这样就不会因为服务器崩溃导致所有请求不可用。限流思路对系统服务进行限流,一般有以下几种方式:①熔断系统在设计之初就考虑了熔断措施。当系统出现问题时,如果短时间内无法修复,系统必须自动做出判断,打开熔断开关,拒绝流量访问,避免大流量请求导致后台过载。系统还应该能够动态监控后端程序的修复状态。当程序恢复稳定后,可以关闭熔断器开关,恢复正常服务。常见的熔断器组件有Hystrix和阿里的Sentinel,两者各有优缺点,可根据业务实际情况选择。②服务降级对系统的所有功能服务进行分级。当系统出现问题,需要紧急限流时,可以将不太重要的功能降级,停止服务,为核心功能释放更多资源。使用。例如,在电商平台中,如果流量突然暴增,可以暂时降级商品评论、积分等非核心功能,停止这些服务,释放机器、CPU等资源,保证用户放置订单正常。而这些降级的功能服务可以等整个系统恢复正常后,再启动它进行订单补货/补偿处理。除了功能降级之外,还可以采用不直接操作数据库,而是全部读取缓存和写入缓存的方式作为临时降级方案。③延迟处理这种模式需要在系统前端建立一个流量缓冲池,将所有的请求缓冲到这个池中,而不是立即处理。然后后端真正的业务处理程序从这个池中获取请求并顺序处理。通常,可以使用队列模式来实现它们。这相当于使用了异步的方式来减轻后端的处理压力,但是当流量较大时,后端的处理能力是有限的,缓冲池中的请求可能无法及时处理,就会出现得到一定程度的延迟。下面具体的漏桶算法和令牌桶算法就是这个思路。④权限处理该模式需要对用户进行分类。通过预设的分类,让系统优先处理对安全性要求高的用户群,其他用户群的请求将被延迟或直接不处理。缓存、降级、限流的区别如下:缓存用于提高系统吞吐量,提高访问速度,提供高并发。降级是指当系统部分服务组件不可用、流量激增、资源枯竭等情况时,暂时屏蔽已经掉出问题的服务,继续提供降级服务,尽可能给用户友好的提醒,以及返回底部数据。它不会影响整个业务流程。问题解决后,服务将在问题解决后再次受限。指的是缓存和降级失效的场景。例如,当达到阈值时,限制接口调用频率、访问次数、库存数量等,在服务不可用之前提前降级服务。只服务好部分用户。限流算法限流算法有很多,常见的有三种,分别是计数器算法、漏桶算法和令牌桶算法,下面会一一说明。①计数器算法简单粗暴,比如指定线程池大小,指定数据库连接池大小,Nnginx连接数等,都属于计数器算法。计数器算法是限流算法中最简单易行的算法。举个例子,比如我们规定对于A接口,一分钟内的访问次数不能超过100次。那么我们可以这样做:一开始我们可以设置一个counter计数器,每有一个请求过来,counter会加1,如果counter的值大于100,且本次请求与第一次请求的间隔还在1分钟以内,说明请求过多,拒绝访问。如果请求与第一次请求的间隔大于1分钟,并且计数器的值还在限流范围内,则重置计数器,就这么简单粗暴。②漏桶算法漏桶算法的思想很简单。水(请求)先进入漏桶,漏桶以一定的速度放水。桶算法可以强制限制数据传输速率。削峰:当有大量流量进入时,会发生溢出,从而提供限流保护服务。缓冲:不能直接向服务器请求,缓冲压力,消费速度是固定的,因为计算性能是固定的。③令牌桶算法令牌桶类似于漏桶,不同的是在令牌桶中放置了一些令牌。服务请求到达后,只有获取token后才会获取服务。比如我们平时去食堂吃饭的时候,一般都是在食堂的窗前排队。这就像漏桶算法。大量的人以一定的速度聚集在食堂的窗外享受服务。如果进来的人太多,食堂装不下,可能会有人站在食堂外面,享受不到食堂的服务,这就叫人满为患。溢出可以继续请求,也就是继续排队,那这有什么问题呢?如果此时有特殊情况,比如有些志愿比较匆忙或者高三马上就要高考了,这种情况就是紧急情况。如果还用漏桶算法,还得慢慢排队,不符合我们的需求。对于很多应用场景,除了限制平均数据传输速率外,还需要允许一定程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更合适。如图所示,令牌桶算法的原理是系统会以恒定的速度往桶中放入令牌,如果需要处理请求,需要先从桶中取出一个令牌,当有桶中没有令牌当卡可用时,服务被拒绝。并发限流简单来说就是设置系统的QPS总数阈值,比较常见。以Tomcat为例,很多参数都是基于这个考虑。比如配置的acceptCount设置响应连接数,maxConnections设置瞬时最大连接数,maxThreads设置最大线程数。在各个框架或组件中,并发限制表现在以下几个方面:限制总并发数(如数据库连接池、线程池)限制瞬时并发数(nginx的limit_conn模块,用于限制瞬时并发数并发连接数)限制时间窗口内的平均速率(如Guava的RateLimiter,nginx的limit_req模块,限制每秒的平均速率)其他包括限制远程接口调用的速率,限制MQ的消费速率。此外,还可以根据网络连接数、网络流量、CPU或内存负载等进行限流。有了并发限流,就意味着在处理高并发时多了一个保护机制。您不必担心瞬时流量导致系统挂起或雪崩,最终损坏服务而不是没有服务。但是,限流需要评估好,不能乱用。否则,一些正常的流量会出现一些奇怪的问题,导致用户体验不佳,造成用户流失。接口限流接口限流分为两部分,一是限制一段时间内的接口调用次数,参考前面限流算法的计数器算法,二是设置滑动时间窗算法.①接口总数控制一定时间内调用的接口总数。可以参考前面的计数器算法,这里不再赘述。②接口timewindow固定时间窗算法(也就是上面提到的counter算法)存在统计区间过大,限流不够准确,没有考虑与之前统计区间的关系和影响的问题在第二个统计区间(第一个区间后半段+第二个区间前半段也是一分钟)。为了解决我们上面提到的关键问题,我们尝试将每个统计区间划分为更小的统计区间和更准确的统计计数。上面的例子,假设QPS可以接受100个query/second,第一分钟前40秒访问量很低,第二个20秒突然变大,持续一段时间,不启动直到第二分钟第40秒下降。按照之前的统计方式,前一秒的QPS是94,下一秒的QPS是92,所以没有超过设置的参数。但是在中间区域,QPS达到了142,明显超过了我们允许的服务请求数,所以固定窗口计数器不靠谱,需要滑动窗口计数器。计数器算法其实是固定窗口算法,只是没有进一步划分时间窗口,所以只有1个格子。可以看出,当滑动窗口的网格划分得越多,即秒精确到毫秒或纳秒时,滑动窗口的滚动会更平滑,限流的统计也会更准确。需要注意的是,占用的空间越大。这部分限流实现是限流的具体实现。简单的说,毕竟没人愿意看长代码。①Guava实现导入包:com.google.guavaguava28.1-jre核心代码:LoadingCachecounter=CacheBuilder.newBuilder().expireAfterWrite(2,TimeUnit.SECONDS).build(newCacheLoader(){@OverridepublicAtomicLongload(Longsecend)throwsException{//TODOAuto-generatedmethodstubreturnnewAtomicLong(0);}});counter.get(1l).incrementAndGet();②令牌桶实现稳定模式(SmoothBursty:令牌生成速度恒定):publicstaticvoidmain(String[]args){//RateLimiter.create(2)每秒产生的令牌数RateLimiterlimiter=RateLimiter.create(2);//limiter.acquire()阻塞方式获取令牌println(limiter.acquire());;try{Thread.sleep(2000);}catch(InterruptedExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}System.out.println(limiter.acquire());;System.out.println(limiter.acquire());;System.out.printlnn(limiter.acquire());;System.out.println(limiter.acquire());;System.out.println(limiter.acquire());;System.out.println(limiter.acquire());;}RateLimiter.create(2)容量和突发量,令牌桶算法允许将一段时间内未被消费的令牌暂时存入令牌桶中,以渐进方式进行突发消费(SmoothWarmingUp:令牌生成缓慢增加speeduntilitmaintainsastablevalue)://平滑限流,从冷启动率(full)到平均消耗率的时间间隔RateLimiterlimiter=RateLimiter.create(2,1000l,TimeUnit.MILLISECONDS);System.out.println(limiter.acquire());;try{Thread.sleep(2000);}catch(InterruptedExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}System.out.println(limiter.acquire());System.out.println(limiter.acquire());System.out.println(limiter.acquire());System.out.println(limiter.acquire());System.out.println(limiter.acquire());System.out.println(limiter.acquire());超时:booleantryAcquire=limiter.tryAcquire(Duration.ofMillis(11));超时时间内能否获取到token,异步执行。③分布式系统限流(Nginx+Lua实现)可以使用resty.lock保持原子特性,请求之间不会重入锁:https://github.com/openresty/lua-resty-lock使用lua_shared_dict存储数据:locallocks=require"resty.lock"localfunctionacquire()locallock=locks:new("locks")localelapsed,err=lock:lock("limit_key")--互斥保证原子特性locallimit_counter=ngx.shared.limit_counter--counterlocalkey="ip:"..os.time()locallimit=5--当前限制大小localcurrent=limit_counter:get(key)ifcurrent~=nilandcurrent+1>limitthen--如果超过当前限制大小lock:unlock()return0endifcurrent==nilthenlimit_counter:set(key,1,1)--第一次需要设置过期时间,key的值设置为1,--过期时间为1秒elselimit_counter:incr(key,1)--第一次在第二次开始时加1到endlock:unlock()return1endngx.print(acquire())作者:迫不及待口琴编辑:Tao嘉龙来源:https://urlify.cn/fuAB7j