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

96秒100亿!如何抵御双11的高并发流量?

时间:2023-03-12 08:39:00 科技观察

今年的双11全民购物狂欢已经进入第十一个年头。1分36秒,交易额达到100亿!比2018快了近30秒,比2017快了近1分半钟!这个速度再次让天猫双11的总交易额破百亿的记录。图片来自Pexels那么如何抵御双11的高并发流量呢?接下来说说高可用的“大杀器”限流降级技术。我们在服务水平协议中常说的N个9就是对SLA的描述。SLA的全称是ServiceLevelAgreement,译为服务水平协议,又称为服务水平协议,表示公有云提供的服务水平和质量。例如,阿里云承诺集群服务在一个服务周期内的可用性不低于99.99%。如果低于这个标准,云服务公司就需要赔偿客户的损失。达到4个九就够了吗?对于互联网公司来说,SLA是网站或API服务可用性的保证。9越多,全年服务时间越长,服务越可靠。4个9的服务可用性听上去很高,但是对于实际的业务场景来说,这个值可能还不够。我们来做个简单的计算,假设一个核心链路依赖20个服务,强依赖且没有配置降级,这20个服务的可用性达到4个9,即99.99%。该核心链路的可用性仅为99.99的20次方=99.8%。如果有10亿个请求,将有3,000,000个失败的请求。在理想情况下,每年仍有17小时的服务不可用。这是一个理想的估计。在实际生产环境中,由于服务发布、宕机等各种原因,情况肯定会比这更糟。对于一些敏感的业务,比如金融,或者对服务稳定性要求高的行业,比如订单或者支付服务,这样的情况是不可接受的。微服务的雪崩效应除了追求服务的可用性,微服务架构中一个无法回避的问题就是服务雪崩。在一条调用链路上,微服务架构的各个服务形成一个松散的整体,一个触发牵一发而动全身。服务雪崩是一个多层次的传导过程。首先是某个服务提供商不可用。由于大量超时,服务调用者不可用,全链路传输,导致系统故障。如何做限流降级上面我们分析过,在大规模微服务架构的场景下,尽可能避免服务雪崩,减少宕机时间,提高服务可用性。提高服务可用性,我们可以从多个方向入手,比如缓存、池化、异步化、负载均衡、队列、降级熔断器等。缓存和队列等手段来增加系统的容量。限流和降级关注的是系统在达到系统瓶颈时的响应,更注重稳定性。缓存和异步提升系统的战斗力,限流降级重在防御。限流和降级的具体实现方法可以概括为八字格言,分别是限流、降级、熔断和隔离。顾名思义,限流和降级限流会提前为每一类请求设置最高的QPS阈值。如果高于设置的阈值,则直接返回请求,不调用后续资源。限流需要结合压力测量来了解系统的最高水位,也是实际开发中使用最广泛的稳定性保障方式。降级是指当服务器压力急剧增加时,根据当前业务情况和流量,对部分服务和页面进行策略性降级,以释放服务器资源,保证核心任务的正常运行。在降级配置方式上,降级一般分为主动降级和自动降级。主动降级是预先配置的,自动降级是当系统出现故障时,比如超时或者频繁故障,会自动降级。其中,自动降级又分为以下几种策略:超时降级失败计数降级故障降级在系统设计中,降级一般与系统配置中心结合,通过配置中心推送。下面是一个典型的降级通知设计。熔断隔离如果一个目标服务调用速度慢或者超时次数多,此时对该服务的调用就会断掉。对于后续的调用请求,目标服务不会再被调用,资源会被快速释放。Fuse一般需要设置不同的恢复策略,如果目标服务情况好转就会恢复调用。服务隔离与前三种略有不同。我们的系统通常会提供不止一种服务,但这些服务在运行时都部署在一个实例或一台物理机上。如果不隔离服务资源,一旦某个服务出现问题,就会影响整个系统的稳定性!服务隔离的目的是避免服务之间相互影响。一般来说,隔离应该关注两个方面,一个是隔离哪里,另一个是隔离哪些资源。在哪里隔离:服务调用涉及服务提供者和调用者。我们所指的资源也是双方的服务器等资源。服务隔离通常可以从提供者和调用者两个方面入手。隔离什么:广义上的服务隔离不仅包括服务器资源,还包括数据库分库、缓存、索引等,这里只关注服务层面的隔离。降级和断路器的区别服务降级和断路器在概念上是相似的。我通过两个场景说说我自己的理解。熔断一般停止服务:典型的股市熔断,如果市场失控,直接关闭市场,不提供服务,是一种保护市场的方式。降级,通常有后备方案:从北京到济南,如果下雨导致航班延误,我可以坐高铁,如果买不到高铁票,可以坐汽车或自驾前往.两者的区别:降级一般是主动的、可预测的,而熔断一般是被动的。服务A降级后,服务B通常会替换它,熔断通常是针对核心链路处理。在实际开发中,融合的下一步通常是降级。常用限流算法设计刚才讲了限流的概念,那么如何判断系统达到了设定的流量阈值呢?这就需要一些限流策略来支持。不同的限流算法具有不同的特性和平滑度。计数器法计数器法是限流算法中最简单易行的算法。假设一个接口限制一分钟内的访问次数不超过100次,维护一个计数器,每有新的请求过来,计数器加1。此时判断如果计数器的值小于限流值并且距离上次请求的时间间隔还在一分钟以内,则允许请求通过,否则拒绝请求。如果超过时间间隔,则必须清除计数器。publicclassCounterLimiter{//初始时间privatestaticlongstartTime=System.currentTimeMillis();//初始计数值privatestaticfinalAtomicIntegerZERO=newAtomicInteger(0);//时间窗口限制privatestaticfinallonginterval=10000;//请求限制privatestaticintlimit=100;//请求计数privateuntAtomicIntegerrequestCountZERO;//获取限流publicbooleantryAcquire(){longnow=System.currentTimeMillis();//在时间窗口内if(nowcapacity){returnfalse;}water=water+1;returntrue;}privatevoidrefresh(){//当前时间longcurrentTime=System.currentTimeMillis();if(currentTime>lastLeakTime){//距离上次漏水的时间间隔longmillisSinceLastLeak=currentTime-lastLeakTime;longleaks=millisSinceLastLeak*ratePerMillSecond;//允许漏水if(leaks>0){//漏光if(water<=leaks){water=0;}else{water=water-leaks;}this.lastLeakTime=currentTime;}}}}令牌桶算法漏桶是用来控制进水速度的,令牌桶是用于控制泄漏。通过控制token,调节流量。假设有一个大小不变的桶,里面存放着令牌(Token)。桶最初是空的,现在以固定速率填充,直到达到桶的容量,多余的令牌将被丢弃。如果令牌没有被消耗,或者消耗的速率低于它们生成的速率,则令牌将继续增加,直到桶满为止。之后生成的令牌会从桶中溢出。最后,桶中可以保存的最大令牌数永远不会超过桶的大小。每当请求到来时,它都会尝试从桶中删除令牌。如果没有令牌,则请求无法通过。publicclassTokenBucketLimiter{privatelongcapacity;privatelongwindowTimeInSeconds;longlastRefillTimeStamp;longrefillCountPerSecond;longavailableTokens;publicTokenBucketLimiter(longcapacity,longwindowTimeInSeconds){this.capacity=capacity;this.windowTimeInSeconds=windowTimeInSeconds;lastRefillTimeStamp=System.currentTimeMillis();refillCountPerSecond=capacity/windowTimeInSeconds;availableTokens=0;}publiclonggetAvailableTokens(){returnthis.availableTokens;}publicbooleantryAcquire(){//更新令牌桶refill();if(availableTokens>0){--availableTokens;returntrue;}else{returnfalse;}}privatevoidrefill(){longnow=System.currentTimeMillis();if(now>lastRefillTimeStamp){longelapsedTime=now-lastRefillTimeStamp;inttokensToBeAdded=(int)((elapsedTime/1000)*refillCountPerSecond);if(tokensToBeAdded>0){availableTokens=Math.min(容量,availableTokens+toBeAdded);lastRefillTimeStamp=now;}}}}这两种算法的主要区别在于漏桶算法可以强制限制数据传输速率,而令牌桶算法可以限制平均数据传输速率,也允许一定程度的突发传输。在令牌桶算法中,只要令牌桶中有令牌,就允许数据以突发方式传输,直到达到用户配置的阈值,因此适用于具有突发特性的流量。LeakyBucket和TokenBucket的对比LeakyBucket和TokenBucket的算法可以用同样的方式实现,只是方向相反,同样的参数得到的限流效果是一样的。主要区别在于令牌桶允许一定程度的爆裂,而漏桶的主要目的是平滑流入速率。考虑到一个临界场景,令牌桶中累积了100个令牌,可以瞬间通过。但是由于下一秒产生Token的速率是固定的,所以令牌桶允许permitsPerSecond的瞬时流量,但是不允许2*permitsPerSecond的流量,漏桶的速度始终是平滑的。使用RateLimiter实现限流Google开源工具包Guava提供了ratelimiter工具类,基于令牌桶算法实现限流,使用方便。RateLimiter使用令牌桶的流量控制算法。RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌后才能执行。比如你希望你的应用QPS不超过1000,那么RateLimiter设置速率为1000后,每秒会往桶里扔1000个token。看方法说明:可以直接应用RateLimter提供的API,acquire会阻塞,类似JUC的信号量Semphore,tryAcquire方法是非阻塞的:publicclassRateLimiterTest{publicstaticvoidmain(String[]args)throwsInterruptedException{//Allow10、permitsPerSecondRateLimiterlimiter=RateLimiter.create(10);for(inti=1;i<20;i++){if(limiter.tryAcquire(1)){System.out.println(""+i+"请求成功");}else{System.out.println(""+i+"requestReject");}}}}总结本文从服务可用性入手,分析业务高峰期通过限流降级来保证服务高可用性的重要性。接下来讨论限流、降级、断路、隔离的概念和应用,介绍常见的限流策略。作者:冰越简介:电商平台架构师,原阿里巴巴中台高级开发工程师,云栖社区专家,专注于分布式系统和高可用架构。