什么是高并发,从字面上理解,就是在某一时刻产生大量的请求,那么多少量称为大量,业界并没有标准的衡量范围。原因非常简单,不同的业务处理复杂度不一样。
而我所理解的高并发,它并不只是一个数字,而更是一种架构思维模式,它让你在面对不同的复杂情况下,从容地选择不同的技术手段,来提升应用系统的处理能力。
但是,并不意味应用系统从诞生的那一刻,就需要具备强大的处理能力,这种做法并不提倡。要知道,脱离实际情况的技术,会显得毫无价值,甚至是一种浪费的表现。
言归正传,那高并发到底是一种怎样的架构思维模式,它对架构设计又有什么影响,以及如何通过它来驱动架构演进,让我们接着往下读,慢慢去体会这其中的精髓。
性能是一种基础
在架构设计的过程中,思考固然重要,但目标更为关键。通过目标的牵引力,可以始终确保推进方向,不会脱离成功的轨道。那高并发的目标是什么,估计你的第一反应就是性能。
没错,性能是高并发的目标之一,它不可或缺,但并不代表所有。而我将它视为是高并发的一种基础能力,它的能力高低将会直接影响到其他能力的取舍。例如:服务可用性,数据一致性等。
性能在软件研发过程中无处不在,不管是在非功能性需求中,还是在性能测试报告中,都能见到它的身影。那么如何来衡量它的高低呢,先来看看常用的性能指标。
每秒处理事务数(TPS)
每秒能够处理的事务数,其中T(Transactions)可以定义不同的含义,它可以是完整的一笔业务,也可以是单个的接口请求。
每秒请求数(RPS)
每秒请求数量,也可以叫做QPS,但它与TPS有所不同,前者注重请求能力,后者注重处理能力。不过,若所有请求都在得到响应后再次发起,那么RPS基本等于TPS。
响应时长(RT)
从发出请求到得到响应的耗时,一般可以采用毫秒单位来表示,而在一些对RT比较敏感的业务场景下,可以使用精度更高的微秒来表示。
并发用户数(VU)
同时请求的用户数,很多人将它与并发数画上等号,但两者稍有不同,前者关注客户端,后者关注服务端,除非每个用户仅发送一笔请求,且请求从客户端到服务端没有延迟,同时服务端有足够的处理线程。
以上都是些常用的性能指标,基本可以覆盖80%以上的性能衡量要求。但千万不要以单个指标的高低来衡量性能。比如:订单查询TPS=100万就认为性能很高,但RT=10秒。
这显然毫无意义。因此,建议同时观察多个指标的方式来衡量性能的高低,大多数情况下主要会关注TPS和RT,而你可以将TPS视为一种水平能力,注重并行处理能力,将RT视为一种垂直能力,注重单笔处理能力,两者缺一不可。
接触过性能测试的同学,可能会见过如下这种性能测试结果图,图中包含了刚才提到过的三个性能指标,其中横坐标为VU,纵坐标分别为TPS和RT。
注:表格中的数据都是理想情况下的,实际上会有上下抖动,而这里只是为了想用它来解释一种现象而已。
图中的两条曲线,在不断增加VU的情况下,TPS不断上升,但RT保持稳定,但当VU增加到一定量级的时候,TPS开始趋于稳定,而RT不断上升。
如果你仔细观察,还会发现一个奇妙的地方,当RT=25ms时,它们三者存在着某种关系,即:TPS=VU/RT。但当RT>25ms时,这种关系似乎被打破了,这里暂时先卖个关子,稍后再说。
根据表格中的数据,性能测试报告结论:最大TPS=65000,当RT=25ms(最短)时,最大可承受VU=1500。
感觉有点不对劲,用刚才的公式来验证一下,1500/0.025s=60000,但最大却是TPS=65000。那是因为,当VU=1500时,应用系统的使用资源还有空间。
再来观察一下表格中的数据,VU从1500增加到1750时,TPS继续上升,且到了最大值65000。此时,你是不是会理解为当VU增加到1750时,使用资源被耗尽了。话虽没错,但不严谨。
注:使用资源不一定是指硬件资源,也可能是其他方面,例如:应用系统设置的最大处理线程。
其实在VU增加到1750前,使用资源就已饱和,那如何来测算VU的临界值呢。你可以将最大TPS作为已知条件,即:VU=TPS*RT,65000*0.025s=1625。也就是说,当VU=1625时,使用资源将出现瓶颈。
调整性能测试报告结论:最大TPS=65000,当RT=25ms(最短)时,最大可承受VU=1625。
有人会问,表格中的RT是不是平均值,首先回答为是。不过,高并发场景对RT会特别敏感,所以除了要考虑RT的平均值外,建议还要考虑它的分位值,例如:P99。
举例:假设1000笔请求,其中900笔RT=23ms,50笔RT=36ms,50笔RT=50ms
平均值 |
P99值 |
P95值 |
P90值 |
25ms |
50ms |
36ms |
23ms |
P99的计算方式,是将1000笔请求的RT从小到大进行排序,然后取排在第99%位的数值,基于以上举例数据来进行计算,P99=50ms,其他分位值的计算方式类似。
再次调整性能测试报告结论:最大TPS=65000,当RT(平均)=25ms(最短)时,最大可承受VU=1625,RT(P99)=50ms,RT(P95)=36ms,RT(P90)=23ms。
在非功能性需求中,你可能会看到这样的需求,性能指标要求:RT(平均)<=30。结合刚才的性能测试报告结论,当RT(平均)=25ms(最短)时,最大可承受VU=1625。那就等于在RT上还有5ms的容忍时间。
既然是这样的话,那我们不妨就继续尝试增加VU,不过RT(平均)会出现上升,但只要控制不要上升到30ms即可,这是一种通过牺牲耗时(RT)来换取并发用户数(VU)的行为。但请不要把它理解为每笔请求耗时都会上升5ms,这将是一个严重的误区。
RT(平均)的增加,完全可能是由于应用系统当前没有足够的使用资源来处理请求所造成的,例如:处理线程。如果没有可用线程可以分配给请求时,就会将这请求先放入队列,等前面的请求处理完成并释放线程后,就可以继续处理队列中的请求了。
那也就是说,没有进入队列的请求并不会增加额外的耗时,而只有进入队列的请求会增加。那么进入队列的请求会增加多少耗时呢,在理想情况下(RT恒定),可能会是正常处理一笔请求耗时的倍数,而倍数的大小又取决于并发请求的数量。
假设最大处理线程=1625,若每个用户仅发送一笔请求,且请求从客户端到服务端没有延迟的条件下,当并发用户数=1625时,能够保证RT=25ms,但当并发用户数>1625时,因为线程只能分配给1625笔请求,那多余的请求就无法保证RT=25ms。
超过1625笔的请求会先放入队列,等前面1625笔请求处理完成后,再从队列中拿出最多1625笔请求进行下一批处理,如果队列中还有剩余请求,那就继续按照这种方式循环处理。
进入队列的请求,每等待一批就需要增加前一批的处理耗时。在理想情况下,每一批都是RT=25ms,如果这笔请求在队列中等待了两批,那就要额外增加50ms的耗时。
VU |
第一批 |
第二批 |
第三批 |
1000 |
1000笔请求 |
|
|
2000 |
1625笔请求 |
375笔请求 |
|
4000 |
1625笔请求 |
1625笔请求 |
750笔请求 |
因此,并不能简单通过VU=TPS*RT=65000*0.03=1950来计算最大可承受VU。而是需要引入一种叫做科特尔法则(Little’s Law)的排队模型来估算,不过由于这个法则比较复杂,这里暂时不做展开。
通过粗略估算后,VU大约在2032,我们再对这个值用上述表格中再反向验算一下。
VU |
第一批 |
第二批 |
RT(平均) |
2032 |
1625笔请求 |
407笔请求 |
(1625*25+407*50)/(1625+407)≈30ms |
最终调整性能测试报告结论:最大TPS=65000,当RT(平均)=25(最短)时,最大可承受VU=1625,RT(P99)=50,RT(P95)=36,RT(P90)=23;当RT(平均)=30(容忍)时,(理想情况)最大可承受VU=2032,RT(P99)=RT(P95)=50,RT(P90)=25。
这就解释了为什么当RT>25ms时,VU=TPS*RT会不成立的原因。不过,这些都是在理想情况下推演出来的,实际情况会比这要复杂得多。
所以,还是尽量采用多轮性能测试来得到性能指标,这样也更具备真实性。毕竟影响性能的因素实在太多且很难完全掌控,任何细微变化都将影响性能指标的变化。
到这里,我们已经了解了可以用哪些指标来衡量性能的高低。不过,这里更想强调的是,性能是高并发的基础能力,是实现高并发的基础条件,并且你需要有侧重性地提升不同维度的性能指标,而非仅关注某一项。
限制是一种设计
上文说到,性能是高并发的目标之一。追求性能没有错,但并非永无止境。想要提升性能,势必投入成本,不过它们并不是一直成正比,而是随着成本不断增加,性能提升幅度逐渐衰减,甚至可能不再提升。所以,有时间我们要懂得适可而止。
思考一下,追求性能是为了解决什么问题,至少有一点,是为了让应用系统能够应对突发请求。换言之,如果能解决这个问题,是不是也算实现了高并发的目标。
而有时候,我们在解决问题时,不要总是习惯做加法,还可以尝试做减法,架构设计同样如此。那么,如何通过做减法的方式,来解决应对突发请求的问题呢。让我们来讲讲限制。
限制,从狭义上可以理解为是一种约束或控制能力。在软件领域中,它可以针对功能性或非功能性,而在高并发的场景中,它更偏向于非功能性。
限制应用系统的处理能力,并不代表要降低应用系统的处理能力,而是通过某些控制手段,让突发请求能够被平滑地处理,同时起到应用系统的保护能力,避免瘫痪,还能将应用系统的资源进行合理分配,避免浪费。
那么,到底有哪些控制手段,既能实现以上这些能力,又能减少对客户体验上的影响,下面就来介绍几种常用的控制手段。
第一招:限流
限流,是在一个时间窗口内,对请求进行速率控制。若请求达到提前设定的阈值时,则对请求进行排队或拒绝。常用的限流算法有两种:漏桶算法和令牌桶算法。
漏桶算法,所有请求先进入漏桶,然后按照一个恒定的速率对漏桶里的请求进行处理,是一种控制处理速率的限流方式,用于平滑突发请求速率。
它的优点是,能够确保资源不会瞬间耗尽,避免请求处理发生阻塞现象,另外,还能够保护被应用系统所调用的外部服务,也免受突发请求的冲击。
它的缺点是,对于突发请求仍然会以一个恒定的速率来进行处理,其灵活性会较弱一点,容易发生突发请求超过漏桶的容量,导致后续请求直接被丢弃。
令牌桶算法,应用系统会以一个恒定的速率往桶里放入令牌,请求处理前,会从桶里获取令牌,当桶里没有令牌可取时,则拒绝服务,是一种平均流入速率的限流方式。
它的优点是,在限制平均流入速率的同时,还能在面对突发请求的情况下,确保资源被充分利用,不会被闲置或浪费。
它的缺点是,舍弃了处理速率的强控制能力,那么如果某些功能依赖外部服务,可能将会让外部服务无法承受压力,导致无法正常返回,而且还浪费了这次获取的令牌。
综上,两种算法并没有绝对的好坏,而是需要根据实际的情况,选择合适的方式,从而在发挥限流作用的同时不会引发其他问题。但在一些秒杀活动中,软件党的高频请求,会很容易触发限流,导致大量正常请求被误杀的问题。
虽然在请求被限流后,会返回友好话术,减轻对客户体验的影响,但也有可能他们的请求,会一直无法得到有效处理,这时候耐心再好的客户也会离开及抱怨。
所以,我们除了使用限流这招外,还得搭配其他的招数组合一起使用,从而让应用系统能够对资源进行合理分配,避免资源浪费,减少正常请求被误杀的情况。
第二招:降频
降频,是在一个时间窗口内,对同一特征的请求进行速率控制。若请求达到提前设定的阈值时,则会对请求进行拒绝。
虽然和限流有点类似,但存在着细微的差别。对限流而言,它并不关心请求方,而只对服务端的速率进行控制,而对降频而言,它会基于某种特征,对请求方的请求速率进行控制。
而降频的目的,是为了减少应用系统资源被不正常的请求所消耗,而导致正常的请求因限流被拒绝的情况发生。它的实现方式也有多种,而且在前端和后端都可以使用。
识别不正常的请求是降频的第一步,也是最关键的一步。一般会制定某种特征+某段时间+请求数量这种三段式的识别规则。
特征可以是账号、会话、IP地址、设备号等,时间一般会是1秒,也可以设置更长。账号+1秒+5笔,意思就是同一个账号在1秒内可以发生5笔请求,但是这里请求数量与限流的设定参考依据不同。
限流大小主要依据性能来决定,而降频中的请求数量,一般会以正常人的交互速率作为参考。所以,并不能因为性能好,就设定账号+1秒+100笔这种识别规则,这不但不科学还会浪费资源。
接下来,有了识别规则还得搭配对应的处置手段,常见的有两种模式:挑战和拒绝。
挑战 |
弹出验证码,输入并验证通过后,可以继续请求 |
仅适用于前端 |
拒绝 |
弹出“请求频繁”提示,且这笔请求将直接被拒绝 |
适用于前端及后端 |
限流会发生误杀,难道降频就不会吗,其实也会发生,特别是用户的网络环境是一个出口IP地址时。所以,如果是基于IP地址特征的识别规则,请求数量建议适当放大。
在降频策略方面,建议配置多层+渐进式的方式,识别规则较为严格的采用挑战模式,识别规则较为宽松的采用拒绝模式,减少因降频而引发的误杀情况,参考如下:
优先级 |
识别规则 |
处置手段 |
1 |
账号+1秒+5笔 |
挑战 |
2 |
账号+1秒+10笔 |
拒绝 |
3 |
IP地址+1秒+20笔 |
挑战 |
4 |
IP地址+1秒+40笔 |
拒绝 |
降频确实可以使应用系统的资源,被合理地分配给请求方,但并不能保证万无一失,特别对于那些技术高超的软件党们,他们仍然可以通过其他方式绕开这种控制手段。
不过,你可以将此视为一种攻防战,通过增强防守的方式,来提高攻击者成本,而攻击者一定会权衡成本和收益,当成本大于收益时,可能就不会有攻击,毕竟没有人会这么无聊透顶。
虽然有了限流和降频这两招,但仍可能无法应对高并发的场景,况且在初期,限流和降频的策略,也无法设计得非常完美。所以,有些时候还得使出最后一招。
第三招:降级
降级,是当应用系统处理超载时,对其服务进行裁剪的一种机制。常见的是应用系统处理阻塞时,会关闭非核心服务,并将资源给到核心服务,从而确保核心服务正常。
经常有人将它与熔断混为一谈,但并非一回事。降级主要是针对应用系统本身,若处理能力不足则可触发,而熔断主要是针对应用系统所调用的外部服务,若外部服务不稳定时则可触发。
当然,两者也有一定的关系,因为当发生熔断时,也可以触发降级机制,比如当同步调用外部服务出现性能问题时,可以降级为异步调用,避免造成线程阻塞而瘫痪。
不过在降级前,必须得先梳理应用系统中的核心服务,可以采用经典的二八原则,将服务划分为20%核心服务+80%非核心服务。而这种分法的意图,是希望让你找到真正重要的核心服务,不然,你会觉得都很重要。
在梳理过程中,建议通过多个维度来进行综合评判,如下是我经常采用的一种梳理方法,你可以将此作为一种参考,并结合自己的服务分类标准进行调整。
首先,可以设计一张类似如下的矩阵图,请尽量地简约它,将应用系统中的各类服务,按照矩阵所设定的不同属性进行分门别类。
|
操作类 |
查询类 |
业务类 |
订单下单 |
商品查询 |
基础类 |
用户登录 |
用户查询 |
然后,将业务类+操作类的挑选出来作为核心服务,你会不会认为这就结束了。不好意思,游戏才刚刚开始。不过你可以试想一下,假设仅保留这些核心服务,会出现什么问题。
用户登录不了无法订单支付,订单查询不了无法订单退货。所以,我们还需引入服务关键路径的概念,可以理解为在使用某个服务前,还必须要使用的其他服务。
分别对挑选出来的核心服务,进行服务关键路径的梳理。
路径1:用户登录——>商品查询——>订单下单
路径2:用户登录——>商品查询——>订单下单——>订单支付
路径3:用户登录——>订单查询——>订单退货
待服务关键路径梳理完成后,再对路径上的所有服务进行合并及去重,将会得到一组新的核心服务:用户登录/商品查询/订单下单/订单支付/订单查询/订单退货。
来计算下核心服务的占比,所有服务14个/核心服务6个,占42.86%,远远超过了20%。所以,建议继续从这些核心服务中,识别更核心的部分,但仍然以服务关键路径为整体。
相比订单下单/订单支付/订单退货这三条服务关键路径,我想订单支付可能会更有价值。最后,我们可以仅将订单支付这条服务关键路径上的服务作为核心服务。
重新再来计算下核心服务的占比,所有服务14个/核心服务4个,占28.57%,虽然还是超过了20%,但这并不是重点,重点是我们已经找到了最核心的服务。
其余的核心服务,可以降级为准核心服务,重组后得到如下这份服务重要程度清单。
核心服务 |
准核心服务 |
非核心服务 |
用户登录 |
订单查询 |
退货进度 |
当拥有这份清单后,若应用系统处理阻塞时,就可以按照非核心服务>准核心服务>核心服务这个顺序依次进行降级。不过,降级不一定要拒绝请求,也可以是限流请求,这样可以减少对服务能力的裁剪力度。
以上只是一种相对较粗的降级策略,如果你想要制定更精细化的降级策略,还需要对每个服务进行优先级的设定,高低依据可以结合自身需要来制定,例如:历史服务使用情况。
当有了限流、降频、降级这三招,基本就能够在资源有限的情况下,让突发请求能够被平滑地处理,将应用系统的资源能够被合理地分配,以及当应用系统处理堵塞时,确保核心服务正常。
取舍是一种权衡
现在,我们已经基本了解了如何衡量性能的指标,以及大致掌握了如何保护应用系统的招数。但这些就是高并发全部了吗,我想说这仅仅只是入门级别。
上述内容,主要是为了让你能够清晰地看到应用系统的性能水位在哪里,以及在资源有限下,当面对突发请求时可以采取哪些招数,能让应用系统安全地存活下来。
存活,即代表着可用性,它也是高并发的一个特性,而且是我认为相对比较重要的特性。设想一下,如果你的应用系统连可用性都无法保证,那再高的性能又有何意义。
对于大部分应用系统而言,大家都会比较关注应用系统的可用性,99.9%不够那就99.99%,甚至还有想做到99.999%的,毕竟可用性的不足会直接影响到业务运作。
但对于一个想成为高并发的应用系统而言,仅单方面关注高可用,肯定无法称得上这个头衔,它仍然还需要在其他特性上具备极佳的表现。
让我们拉回到最初的目标,性能是高并发的目标之一,不过,这里我们不再谈论性能指标,而是来研究如何来提升性能。因为,高性能是高并发的另外一个重要特性。
想要提升性能,势必投入成本。随着成本不断增加,性能提升幅度逐渐衰减。这两句话,是不是觉得有点耳熟,但不管你是否还记得,先让我在这里打个问号再说。
在架构设计的过程中,你是否经常会听到“取舍”的这个词,它是通过牺牲一种能力来换取另外一种能力的方式,这些能力可以是性能、可用性、数据一致性或是其他能力。
等等,你是不是突然有意识到,提升性能并不只有投入成本这种方式,至少是在硬件资源方面,我们还可以通过牺牲一种能力去换取。那到底选择牺牲哪种能力呢,牺牲可用性,一般不会第一时间考虑,那是不是可以考虑牺牲数据一致性。
但在考虑前得先声明一下,所谓牺牲数据一致性,并不是完全不要,而是将数据强一致性降级为最终一致性。而对于数据最终一致性的理解,就是在数据更新后,要过一段时间后才能看到,而时间的长短就代表着牺牲了多少。
但并不是说,所有情况都必须牺牲数据一致性来提升性能,有些时候也可以考虑牺牲其他能力。但在取舍前,得先弄清楚当前要什么,但更需要弄清楚当前可以失去什么,不合理的取舍,不但无法换取收益,反而还会引来更多的问题。
情况1:数据缓存
缓存,是高并发架构设计中一种不可或缺的能力,一般是指那些经常被访问的热点数据,可以将它放入缓存中,从而提升数据被读取的效率。
但是否所有的数据都适合放入缓存中,如果是静态数据,那么你可以很安心地放入。原因很简单,静态数据不会更新,那么缓存和数据源始终保持一致,而且就算缓存中的数据丢失了,至少还有一份在数据源。
通过将静态数据缓存,可以很轻易地提升静态数据的访问性能,甚至可能是几十倍的效果。但应用系统中还有大量的动态数据,仅提升静态数据可能对总体的提升并不一定显著。
你是不是想说,那就把动态数据也放入缓存中不就行了。在下这个决定前,建议你先想一下,动态数据是会更新的,这就意味着动态数据在放入缓存后,当数据源中的数据被更新后,再次访问返回的都是更新前的数据,这种效果你是否可以接受。
我想应该没有人会接受吧,而你是不是又想说,设置下缓存会过期不就能解决了。没错,但得等过期后才能解决,那还没过期前呢,这种方式只能缓解,但并不能根治,而且还会引入一个新的问题,请问过期设置多久才合适。
设置缓存5秒钟过期,可能永远无法命中缓存,而且不但没有提升性能,还增加了代码复杂度,有点画蛇添足的感觉。设置缓存5分钟过期,命中缓存的几率可能会提高,但缓存后在5分钟内的如果数据更新,要从缓存开始往后推5分钟才能看到。
即:第1分钟缓存,第1-6分钟内的任何数据更新,要第6分钟后才能看到。
所以,如果你无法容忍这种情况,请你不要滥用缓存,虽然性能提高了,但问题可能也出现了。反之,如果你可以容忍这种情况,那就可以这么操作,而至于过期设置多久,可以结合业务场景及使用频率综合来评估,毕竟不同的业务容忍度是不同的。
对于是否要将动态数据进行缓存,本质上,其实就是一种取舍,是一种性能与数据一致性的权衡,而缓存的过期时长,就像是保持这种平衡的支点,从而让这种牺牲变得更有意义。
情况2:单机限流
限流,前面已经有介绍过,它有两种常用的限流算法,漏桶算法和令牌桶算法。不过,这两种算法都仅支持单机限流,不支持全局限流。
单机限流,就是对单节点设定一个限流阈值,如果单节点上的请求到达阈值,则会拒绝请求。例如:限流阈值=每秒100次请求,如果在1秒内单节点上,有第101次的请求则拒绝。
全局限流,就是对一组节点设定一个限流总阈值,如果这组节点上的汇总请求到达阈值,则会拒绝请求。例如:10个节点的限流总阈值=1000次请求,如果在1秒内这组节点上,汇总有第1001次请求则拒绝,不过单节点上有超过第100次的请求也会接受。
这么看下来,感觉单机限流控制能力更厉害一点,它能保证单节点的请求不会超过100次。而全局限流在极端情况下,单节点都有可能在1秒内会接受1000次请求。当然,这种情况的可能性比较低,比如在突发请求时,9个节点同时宕机。
既然如此,那全局限流有存在的意义吗,难道这就是漏桶算法和令牌桶算法都不支持全局限流的原因。全局限流就真的没有存在的意义吗。存在即合理,既然存在,那就一定有它存在的道理。
换个情况,还是将10个节点为一组,不过这次换成采用单机限流。问题来了,每个节点的限流阈值该如何设定,如果采用平均分配,则限流阈值=每秒100次请求,让我们来测试一下,在1秒内依次发出1000次请求,会发生什么现象。
结果是在第100次请求后,从第101次到第1000次的请求中,可能有些请求会发生被拒绝的情况,而且请求一会儿成功一会儿拒绝,没有任何规律。原因可能是10个节点请求负载不均所引发的,导致某个节点提前超过了100次请求。
基于以上情况,最终1000次请求没有全部成功,这种情况等同于降低了应用系统的吞吐能力。而在实际情况中,就算采用轮询的负载算法,请求数不均的可能性仍然还是会存在的。这么一看,单机限流好像也有缺陷。
估计你已经被我说晕了吧,让我们整体再重新梳理一遍,并对两种不同限流模式的影响进行对比。不过,这次还加上每秒不同的请求数量。
每秒请求 |
单机限流 |
全局限流 |
100笔 |
无影响 |
无影响 |
1000笔 |
少量请求拒绝 |
耗时小幅波动 |
10000笔 |
请求拒绝>9000 |
请求拒绝=9000 |
注:每个节点的处理能力为100笔/秒
两种限流对比下来,单机限流更强调单机的控制范围,但可能会造成额外的请求拒绝,但对单节点不会造成性能压力,而全局限流更强调整体的控制范围,虽不会造成额外的请求拒绝,但可能会对单节点造成性能压力,引发性能过载。
除此之外,全局限流还是一种采用中心化的设计思路,因此在网络开销方面,还会产生额外的性能损耗,这种损耗在请求量少的时候估计还可以容忍,但在高并发的情况下可能是场灾难,因为在每次限流判断前,还会产生一次网络开销。
所以,不能为了想要实现更精准的限流,就盲目地采用全局限流,它将在高并发的情况下损耗更多的性能。而单机限流所额外造成的少量请求拒绝,在某些情况下,可以考虑采用某些技术手段进行补偿。
不过,不管是单机限流还是全局限流,似乎都和数据一致性没有关系。但事实上,全局限流这种精准限流的方式,也可以视为另一种一致性的表现,而单机限流就是通过对这种一致性的牺牲,来减少性能损耗,何尝不是提升性能的另一种方式。
以上,只是简单列举了两种不同情况下的取舍,而在高并发架构上,可取舍的地方远不止这些。你得知道,高并发的每一处设计或每一份设计方案的背后,都曾是通过不断地取舍所获得的,而没有取舍的高并发架构决策,将会显得毫无说服力。
取舍不但可以作为高并发架构决策的有力武器,也将是驱动架构演进最合理的一种方式。但要切记,取舍的方向并不是一成不变的,而是会随着外界环境的变化而变化,它将是一种独特的艺术。
写在最后
高并发的魅力之处,就在于它没有唯一的答案,而答案是需要我们以不同的业务场景作为线索去不断地寻找,这种寻找的过程也是一种不断思考的过程,这就是我为什么说高并发是一种架构思维模式。
本文从浅到深依次讲述了性能是实现高并发的基础条件,控制是实现资源最大化利用的方式,以及如何通过取舍来换取当前应用系统更所需的能力,但这些仅仅只是高并发世界里的一个角落。因篇幅有限,今天就暂告一段落。
最后想说,高并发其实并不可怕,可怕的是你知其然而不知其所以然。对于追求技术的你,需要不断地拓宽你的技术深度与广度,才能更好地掌握高并发,以及运用高并发的思维模式来提升应用系统处理能力。