精华内容
下载资源
问答
  • 交流限流器
    千次阅读
    2022-01-24 10:10:19

    本文我们介绍另外一种限流器—令牌桶(TokenBucket)。

    在这里插入图片描述

    令牌桶(TokenBucket)简介
    令牌桶实现的基本思想

    令牌桶,顾名思义,是一种通过让请求被处理前先行获取令牌,只有获取到令牌的请求才能被放行处理的一种限流方式。令牌桶的实现包含两个方面:

    一方面是按固定的速率来产生令牌并存入桶中,如果令牌数量超过桶的最大容量则直接丢弃掉。
    一方面当有请求时先从桶中获取令牌,获取到令牌后才能通过进行处理,否则被直接丢弃或等待获取令牌。
    令牌桶与漏桶(LeakyBucket)的区别

    令牌桶与漏桶的区别在于漏桶控制的是请求被处理的速率。即当有请求的时候,先进入桶中进行排队,按固定的速率流出被处理;而令牌桶控制的是令牌产生的速率。即当有请求的时候,先从令牌桶中获取令牌,只要能获取到令牌就能立即通过被处理,不限制请求被处理的速度,所以也就可以应对一定程度的突发流量。如图所示二者的区别:

    在这里插入图片描述

    比如有100个请求同时进入。现在假设漏桶的速率是每10ms处理一个请求,那么要处理完这100个请求需要1秒钟,因为每处理完1个请求,都需要等待10ms才能处理下一个请求。

    如果是令牌桶,假设令牌桶产生令牌的速率也是每10ms产生一个,那么1秒钟就是产生100个令牌。所以,一种极端的情况就是当这100个请求进入的时候,桶中正好有100个令牌,那么这100个请求就能瞬间被处理掉。

    golang中的time/rate包
    golang.org/x/time/rate 包就是基于令牌桶实现的。我们先来看下该包的使用,然后再分析该包的具体实现。

    func main() {
      //构造限流器。第一个参数代表qps,即每秒钟能够产生多少令牌,第二个参数是指桶的最大容量,即最多能容下5个token
    	limiter := NewLimiter(10, 5)
    	
    	for i := 0; i < 10; i++ {
    		time.Sleep(time.Millisecond * 20)
    		//Allow指去获取令牌,如果没获取到,返回false
    		if !limiter.Allow() {
    			fmt.Printf("%d passed\n", i)
    			continue
    		}
    		
    		//说明请求通过Allow获取到令牌了,继续处理请求
    		fmt.Println("%d droped\n" i)
    		//todo 对请求的后续处理
    	}
    }
    
    复制代码
    

    time/rate实现原理
    在简介的部分我们提到,令牌桶需要按固定速率生成Token。直观的理解就是在令牌桶的实现中会有一个定时任务不断的生成Token。但在 Golang 的 time/rate 中的实现, 并没有单独维护一个定时任务,而是采用了 lazyload 的方式,直到每次有请求消费之前才根据时间差更新 Token 数目,同时通过计数的方式来计算当前桶中已有的Token数量。

    Token的生成和消耗
    在开头处我们提到,令牌桶是以固定的速率产生Token,该速率就是我们在使用NewLimiter构造一个限流器时指定的第1个参数limit,代表每秒钟可以产生多少个Token。

    func NewLimiter(r Limit, b int) *Limiter {
    	return &Limiter{
    		limit: r,
    		burst: b,
    	}
    }
    复制代码
    

    那么换算一下,就可以知道每生成一个Token的间隔时间 perToken = 1秒/limit。假如我们指定的limit参数是100,那么perToken=1秒/100 = 10毫秒。

    上文提到,time/rate包使用的是懒加载的方式生成的Token。什么是懒加载的方式呢?就是当有请求到来时,去桶中获取令牌的同时先计算一下从上次生成令牌到现在的这段时间应该添加多少个令牌,把增量的令牌数先加到总的令牌数据上即可,后面被取走的令牌再从总数中减去即可。所以,我们在限流器中应该记录下最近一次添加令牌的时间和令牌的总数,Limiter的结构体会是如下这样:

    type Limiter struct {
    	limit  Limit   //QPS,一秒钟多少个token
    	burst  int     //桶的容量,即最大能装多少个令牌
    	tokens float64 //当前的token数量
    	last time.Time //last代表最近一次更新tokens的时间
    }
    复制代码
    

    好了,到这里,我们有了生成一个token的时间间隔、最近一次更新tokens的时间、当前时间、当前的token数量四个属性,就能很容易的计算每次有请求获取令牌时,应该生成的令牌数量以及当前桶中总剩余的令牌数了:

    tokens += (当前时间 - 最近一次更新tokens的时间last) / 时间间隔
    复制代码
    消耗的话,就是看当前的令牌总数是不是大于0就好了,如果大于0,相当于该请求可以获取令牌,从tokens中减1,代表给该请求发放了一个令牌,该请求拿着令牌就能被通过进行处理了。

    那到这里是不是该算法就结束了呢?并不是。那该TokenBucket是如何应对突发流量呢?

    如何应对突发流量
    所谓突发流量,就是在某个时刻的流量突然比平时的流量要高。光有突发流量还不够,得系统能够应对才行,即能够正常处理,否则就不叫应对突发流量了。那令牌桶是如何应对突发流量的呢?就是通过令牌桶缓存到的令牌来应对的,再加上令牌桶的最大容量约束,不会无限制的让流量通过。 下面我们具体来看下应对突发流量的过程。

    在这里插入图片描述

    假设生成令牌的速率是每秒100个。而平常的请求平均值也就是每秒80个。也就是说每秒钟令牌数能剩余20个,那这剩余的令牌就是用来应对突发流量的。例如在10秒后,就会有200个令牌剩余,如果这个时候比平常多来200个请求,那么令牌数也足以让每个请求都领取到令牌从而被正常处理。

    那么,问题就又来了,如果在很长一段时间内,我们的系统请求数都很平稳,这样我们就能积攒下很多剩余的令牌,如果剩余的令牌数很多,比如积攒了一千万个了,突然来了一波流量,假设也是一千万,按道理这一千万个请求都能获取到令牌,都能够被处理。可问题是我们的计算机系统本身的资源缺不足以应付这么多请求了,那该怎么办呢?

    这个时候我们的令牌桶的最大容量属性就该上场了,即Limiter结构体中的burst字段。burst字段是限制该桶能够存储的最大令牌数,令牌积攒的数量超过该值后,就直接丢弃,不再进行积攒了。该字段也代表我们的系统能够应对的最大的突发流量数。 例如,一种极端的情况,在一段时间内,一个请求都没有,但令牌会按照固定速率一直产生,这时令牌数达到了最大值burst。突然有一波请求来了,假设是burst+n个,那这波流量中也就只有burst个请求能被正常处理,其他的就被拒绝了。因为我们只有burst个令牌。 所以,burst值代表了我们系统应对突发流量的最大值。

    数值溢出问题
    我们在一开始讲该算法的实现时首先要计算从最后一次更新tokens数量到当前这段时间内产生的令牌数,以及令牌总的数量,一般的计算方式应该如下:

    // elapsed表示最后一次更新tokens数量的时间到当前的时间差
    	elapsed := now.Sub(last)
    	// delta 具有数值溢出风险, 表示elapsed这段时间应该产生的令牌数量
    	delta := elapsed.Seconds() * float64(limit)
    	
    	//tokens 表示当前总的令牌数量
    	tokens := lim.tokens + delta
    	if burst := float64(lim.burst); tokens > burst {
    		tokens = burst
    	}
    复制代码
    

    这里有什么问题呢? 如果last值很小,那么elapsed就会很大,而如果此时指定的token生成速率,即limit值也很大的话,那么一个大值 乘以 一个大值,结果就很可能会溢出。

    那该怎么办呢? 我们知道,令牌桶有一个最大值burst,如果超过这个burst,那么多余的其实是没用的。 因此,我们就可以先计算要填满这个令牌桶最多需要多长时间maxElapsed,如果时间差now.Sub(last)已经超过了该值,那么说明令牌数就应该能达到最大值burst了。反之,说明now.Sub(last)是一个较小的值,继续计算这段时间应该生成的令牌数即可,这样就规避了大值相乘可能溢出的问题。 如下是time/rate包中的实现:

    maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
    elapsed := now.Sub(last)
    if elapsed > maxElapsed {
    	elapsed = maxElapsed
    }
    
    delta := lim.limit.tokensFromDuration(elapsed)
    
    tokens := lim.tokens + delta
    if burst := float64(lim.burst); tokens > burst {
    	tokens = burst
    }
    复制代码
    

    float64精度问题
    我们在上面Limiter的结构体中会注意到,tokens的类型是float64的。你可能会问,难道令牌还有小数点,令牌数量不应该是整数吗? 是的,令牌数是有可能是小数的。为什么呢?。

    假设,我们指定的生成令牌的速率是每秒产生965个令牌,那么每生成一个令牌的间隔是多少呢?大约是每1.0362毫秒产生一个令牌。那么如果是在100毫秒这段时间会产生多少个令牌呢?大约103.62个令牌。

    好了,既然是float64,那么在计算给定时间段内产生的tokens总数时就会有精度问题。我们来看看time/rate包的第一版的实现:

    func (limit Limit) tokensFromDuration(d time.Duration) float64 {
    	sec := float64(d/time.Second) * float64(limit)
    	nsec := float64(d%time.Second) * float64(limit)
    	return sec + nsec/1e9
    }
    复制代码
    

    time.Duration 是 int64 的别名,代表纳秒。分别求出秒的整数部分和小数部分,进行相乘后再相加,这样可以得到最精确的精度。

    总结
    TokenBucket是以固定的速率生成令牌,让获得令牌的请求才能通过被处理。令牌桶的限流方式可以应对一定的突发流量。在实现TokenBucket时需要注意在计算令牌总数时的数值溢出问题以及精度问题。

    最后
    如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑

    如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star:http://github.crmeb.net/u/defu不胜感激 !

    PHP学习手册:https://doc.crmeb.com
    技术交流论坛:https://q.crmeb.com

    更多相关内容
  • 交流饱和铁芯型故障限流器(SCFCL)的研究现状和发展历程进行了综述。首先介绍了直流偏置型、永磁体偏置型、超导型和混合偏置型这4类SCFCL的工作原理、拓扑结构、等效电路和应用实例,比较并分析了4类SCFCL的功能...
  • 针对电力系统的短路电流限制问题,研究了一种新型桥式高温超导故障限流器。该限流器主要由两个超导带材绕制的线圈反向并联而成。线路正常工作时,并联线圈的感抗很小,因此对电路无影响,短路故障发生时,其中一个...
  • 有效防止高温失灵—PTC热敏电阻用作LED限流器、电子技术,开发板制作交流
  • 摘要:介绍了一种适用于固态限流器的多输出高压隔离谐振式电源,给出了主电路拓扑结构,分析了其工作原理,并用PSpice对其进行了仿真验证,最后给出了实验结果。 理论分析和实验结果证明,负载谐振模式使负载电流...
  • 近日,STMicroelectronics为高效功率转换提供限流芯片——STIL02-P5 和STIL04-P5。在很多的交流/支流电源转换中,都采用桥式电路。但是桥式电路会产生瞬间电流,这部分电流对功率转换不起作用,同时,常常会在PCB...
  • 基于Step-Down PWM电源管理芯片的PFM限流比较电路设计、电子技术,开发板制作交流
  • 一路数字输出用于检测交流适配器是否插入。 MAX8730提供0.5%的电池电压调节精度,因此提高了电池容量并极大地缩短了充电时间。该器件可通过硬件连线或微处理控制充电电流或电压。另外,MAX8730还可通过两
  • 一路数字输出用于检测交流适配器是否插入。 MAX8730提供0.5%的电池电压调节精度,因此提高了电池容量并极大地缩短了充电时间。该器件可通过硬件连线或微处理控制充电电流或电压。另外,MAX8730还可通过两个p沟道...
  • 限流型保护电路

    2021-02-03 14:19:00
    限流型保护电路、电子技术,开发板制作交流
  • 基于柔性直流电网的故障特征,结合国内外研究成果,从交流限流、换流器限流以及直流侧限流3个方面分析了柔性直流电网各类限流技术和方法的原理及性能,对相关限流技术和方法进行了仿真测试和比较。基于对比分析...
  • 为了降低交流系统短路故障给基于模块化多电平换流器的统一潮流控制器(MMC-UPFC)带来的大电流冲击风险,保障电力电子设备的安全运行,提出了一种基于分裂电感的限流式MMC-UPFC。通过对桥臂电感的分裂设计和串联变压器...
  • 德力西RN1 型高压限流熔断pdf,德力西RN1型高压限流熔断:本产品使用于户内交流50Hz,额定电压6~35kV系统中作为电力设备及电力线路的过载或短路保护。
  • 德力西RN 型高压限流熔断pdf,德力西RN型高压限流熔断:本产品使用于户内交流50Hz,额定电压6~35kV系统中作为电力设备及电力线路的过载或短路保护。
  • 同时,交流电流被限制为逆变的最大额定值,它取决于注入的无功电流。 这有助于克服可能导致逆变损坏或断开连接的交流过电流。 该控制方案还通过根据电网规范要求注入无功电流来确保电压支持和功率平衡。 在各种...
  • 德力西XRNT1型变压器保护用高压限流熔断pdf,德力西XRNT1型变压器保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压12kV的系统,可与其他开关电器如负荷开关、真空接触配合使用,作为电力变压器及其它...
  • 德力西XRNM1型电动机保护用高压限流熔断pdf,德力西XRNM1型电动机保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压3.6KV系统.可与其他保护电器(如开关,真空断路)配合使用,作为高压电动机及其它电力设备...
  • 德力西XRNT1-12/200型变压器保护用高压限流熔断pdf,德力西XRNT1-12/200型变压器保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压12kV的系统,可与其他开关电器如负荷开关、真空接触配合使用,作为电力...
  • 德力西XRNT1-12/125型变压器保护用高压限流熔断pdf,德力西XRNT1-12/125型变压器保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压12kV的系统,可与其他开关电器如负荷开关、真空接触配合使用,作为电力...
  • 德力西XRNM1-3.6/400型电动机保护用高压限流熔断pdf,德力西XRNM1-3.6/400型电动机保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压3.6KV系统.可与其他保护电器(如开关,真空断路)配合使用,作为高压电动...
  • 德力西XRNT1-12M/125型变压器保护用高压限流熔断pdf,德力西XRNT1-12M/125型变压器保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压12kV的系统,可与其他开关电器如负荷开关、真空接触配合使用,作为...
  • 德力西XRNT1-12M/50 型变压器保护用高压限流熔断pdf,德力西XRNT1-12M/50型变压器保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压12kV的系统,可与其他开关电器如负荷开关、真空接触配合使用,作为...
  • 德力西XRNM1-7.2/224型电动机保护用高压限流熔断pdf,德力西XRNM1-7.2/224型电动机保护用高压限流熔断:本产品适用于户内交流50Hz,额定电压7.2KV系统.可与其他保护电器(如开关,真空断路)配合使用,作为高压电动...
  • 网关限流分布式解决方案

    千次阅读 2022-01-27 11:45:43
    网关限流分布式解决方案+单机网关限流背景介绍实现的功能技术选型限流算法漏桶算法令牌桶算法令牌桶和漏桶对比两种算法的区别令牌桶的实现Lua处理过程 背景介绍 微服务网关模块将实现网关集群部署,并且登录、鉴权和...

    背景介绍

    微服务网关模块将实现网关集群部署,并且登录、鉴权和配额管理都会依赖另一个权限系统,为实现分布式下的并发控制和配额管理,提出解决方案。

    实现的功能

    在分布式下网关集群中,限制每个用户访问每个方法并发访问量和每日访问总量,也就是限流,准确来说是并发控制和配额管理

    技术选型

    Redis + Lua脚本实现令牌桶限流 ActiveMQ实现接收权限系统限额调整

    限流算法

    常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。

    漏桶算法

    漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

    • 一个固定容量的漏桶,按照常量固定速率流出水滴;
    • 如果桶是空的,则不需流出水滴;
    • 可以以任意速率流入水滴到漏桶;
    • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

    在这里插入图片描述

    令牌桶算法

    令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:

    • 假设限制2r/s,则按照1秒的固定速率往桶中添加令牌;
    • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
    • 当一个n个请求到达,将从桶中删除n个令牌,接着请求被通过;
    • 如果桶中的令牌不足n个,则不会删除令牌,且该请求将被限流(要么丢弃,要么缓冲区等待)。

    在这里插入图片描述

    令牌桶和漏桶对比

    • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;
    • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
    • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;
    • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;
    • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;
    • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

    两种算法的区别

    两者主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,所以它适合于具有突发特性的流量。

    令牌桶的实现

    分布式系统解决方案是用Redis+Lua脚本实现使用redis进行限流,redis的单线程操作特性来执行lua脚本,通过lua脚本来保证原子性(将脚本作为一个整体执行,中间不会插入其他命令,无需使用事务)而且可以减少网络开销(多个请求通过脚本一次发送,减少网络延迟),这样的方案很好地解决了分布式环境下多实例所导致的并发问题。因为使用redis设置的计时器和计数器均是全局唯一的,不管多少个节点,它们使用的都是同样的计时器和计数器,因此可以做到非常精准的流控令牌桶限流。

    Redis中存储类型设置为Hash存储,存储令牌桶对象的信息,在redis中存储示例如图:
    在这里插入图片描述

    • Hash对象key为用户id+方法的组合(上图用户id为1,方法为maptileInfo)
    • last_mill_second:上一次操作时间(毫秒值)
    • stored_permits:目前桶中可用的令牌数
    • max_permits:桶容量
    • oneSecondNum:每秒生成令牌的个数
    • stored_everyday_limit:本日当前还可以消耗的限额数(本日当前余额)
    • everyday_limit:每日限额

    Lua处理过程

    1.判断当前时间和上次操作时间间隔,如果不是同一天的话本日当前限额数=本日限额
    2.计算到当前该放多少令牌(当前时间-上次操作时间间隔)* 每秒生成令牌数
    3.计算当前剩余多少令牌 = min(桶容量, 目前桶中令牌数+当前该放的令牌数) ,最大值为桶的容量
    4.如果当前剩余多少令牌 >=1,并且本日当前限额数>=1,则消耗令牌放行

    在这里插入图片描述

    其他解决方案

    思考:网关限流redis+lua的分布式解决方案,如果请求量特别大,保证请求的顺序和时间,包括原子性等问题,这些还需要进一步压测。还有令牌桶容量,时间间隔,每秒生成速率怎么设置合适,希望有能力,有兴趣的大神交流、指教。

    单机网关限流

    单机网关限流比较简单,不需要考虑集群问题,令牌桶实现方法:
    单机令牌桶

    展开全文
  • 介绍交流电动机升压限流软起动技术和软起动控制,给出交流电动机软起动的基本概念,分析升压限流软起动的基本原理,并对软起动控制的软硬件设计作介绍,重点讨论了8098单片机为核心,以无触点可控硅为主要控制...
  • 限流功能动态过程分析基础上,提出一种通过调整晶闸管触发相位角的故障电流调节方法,给出限流阻抗的调节范围,从而解决限流阻抗与电网原有过流保护的配合问题;最后,采用PSCAD/EMTDC仿真软件和实验室样机,验证...
  • 本文是源码分析 Sentinel 系列的第十三篇,已经非常详细的介绍了 Sentinel 的架构体系、滑动窗口、调用链上下文、限流、熔断的实现原理,相信各位读者朋友们对Sentinel有一个较为体系化的认知了,这个时候是该开始...

    本文是源码分析 Sentinel 系列的第十三篇,已经非常详细的介绍了 Sentinel 的架构体系、滑动窗口、调用链上下文、限流、熔断的实现原理,相信各位读者朋友们对Sentinel有一个较为体系化的认知了,这个时候是该开始如何在生产环境进行运用了。

    本文将以 Dubbo 服务调用为案例剖析场景,尝试对官方提供的 Dubbo 适配器做一个研究学习并对此做出自己的评价,抛出我的观点,期待与大家共同探讨,交流。

    一个 Dubbo RPC 的简易调用过程如下图所示:
    在这里插入图片描述
    消费者会维护一个服务提供者列表,然后再发一起一个服务调用的时候会首先根据负载均衡算法从中选择一个服务提供者,然后发起 RPC 调用,在请求真实发送之前会依次通过客户端设置的过滤器链(Filter),然后经过网络传输到到达服务提供者,并执行完服务提供者端的 Filter,最后进入到业务逻辑执行并返回结果。

    Sentinel 与 Dubbo 的整合就是利用了 Dubbo 的 Filter 机制,为 Dubbo 提供对应的 过滤器,无缝实现限流、熔断等功能,做到业务无感知,即业务代码无需使用 Sentinel 相关的 API。

    接下来请大家带着在 Dubbo 中如何使用限流、熔断方面来看官方给出的解决方案。

    思考题:在看接下来的内容之前,建议大家稍作停顿,思考一下,在服务调用模型中,限流、熔断通常在哪个端做比较合适。

    1、从消费端来看限流与熔断

    在这里插入图片描述从消费端的视角,虽然提供了服务端的负载均衡,但从客户端不管是向192.168.1.3还是向192.168.1.4发送RPC调用,都会经过同一个 Sentinel Dubbo Filter。这个看似简单明了,但这是我们接下来思考的最基本最核心的点。

    我们先来看看官方提供的 Dubbo 适配器的核心实现:
    SentinelDubboConsumerFilter#invoke
    在这里插入图片描述
    消费端这边使用到了两个资源名称,一个是接口级别,例如 com.demo.service.UserService,另外一是方法级别,例如 com.demo.servcie.UserServce#findUser(Ljava.lang.String)。
    定义了两个资源后,Sentinel 会使用滑动窗口机制,为上述两个资源收集实时的调用信息,为后续的限流、熔断提供数据依据。

    限流规则是依附于具体某一个项目的,例如如下图所示:
    在这里插入图片描述限流、熔断都是根据资源级别,如果需要对消费端的调用进行限流的话,就需要为这两个资源配置对应的限流规则,如果不配置则默认通过,表示不限流。

    1.1 服务调用端(消费方)是否需要配置限流规则

    在 dubbo 的服务调用场景中,在消费端设置限流的规则的话,这个调用链是针对整个集群所有服务提供者的,例如当前集群中包含3个服务提供者,每个服务提供者用于1000tps的服务能力,那消费端的限流,应该配置的限流TPS应该为3000tps,如果设置为1000tps,则无法完整的利用服务端的能力,基于这样的情况,通常消费端无需配置限流规则。

    那是不是说消费端就没必要配置限流规则呢?其实也不是,有如下这个场景,例如调用第三方外部的计费类服务接口,对方通常为特定的账户等级设置对应的TPS上限,如果超过该调用频率就会抛出错误,这种情况还是需要设置限流规则,确保消费端以不超过要求进行调用,避免业务异常。

    1.2 服务调用端(消费方)是否需要配置熔断

    引入熔断的目的是避免服务端单节点响应慢而导致这个服务不稳定,例如示例中有3个服务提供者,如果192.168.1.3的服务提供者由于触发了BUG导致响应时间大大增加,导致发往该服务提供者的请求大概率超时,在这样的情况下希望在接下来某段时间内消费方发往这这个服务提供者的请求快速熔断降级,返回错误,由客户端重试其他服务提供者。其实现效果如下:
    在这里插入图片描述
    当前的 Sentinel 默认是否能满足上述的需求呢?

    我们以 Sentinel 基于异常比例熔断策略来进行阐述,如果资源的调用异常比例超过一定值是会触发降级熔断,抛出 DegradeException 异常。

    由于总共只有三个服务提供者,其中发往192.168.1.3的请求大概率会由于超时失败,则异常比例会超过设置的熔断降级规则,会触发降级,造成的效果是整个服务调用都会发送熔断降级,即调用192.168.1.4,5两个请求都不会被熔断,造成整个服务调用不可用,与期望不一致。即还是会出现一个节点的不稳定而导致整个服务不稳定的情况。

    其造成的根本原因是因为其资源的定义并没有包含服务提供者的信息,改进的初步方案:

    1. 在过滤器中再定义一个资源,加上服务提供的IP与端口号,例如 SphU.entry(“com.d.s.UserService@ip:port”),对单个服务提供者进行单独收集调用信息,并且需要提供一可配置的项,用来标记该类型的资源在做熔断判断可使用某一个资源的配置,例如配置为 com.d.s.UserService,表示用这个配置规则来判断熔断。
    2. 在熔断规则判断的时候,该资源使用被引用资源的熔断规则进行判断。

    最后来解答一下,熔断规则通常只需要配置在调用方即可。

    2、从服务来看限流与熔断

    由于服务端看限流与熔断就比较简单,因为服务端与客户端存在一个非常大的区别是客户端存在负载均衡机制,一个消费端对于同一资源来说,会调用多个服务提供者,而服务提供者对于一个资源来就是其自身,故限流规则,熔断规则都是针对个体,其复杂度降低。

    为了知识体系的完备性,我们来看一下 Sentinel Dubbo 在服务端的适配器的实现。

    SentinelDubboProviderFilter#invoke
    在这里插入图片描述
    这里有二个关键点:

    1. 使用了 ContextUtil 的 entry 方法,定义本次调用的上下文环境名称为:resourceName,默认为接口名与方法名、参数列表,例如 com.d.s.UserServce#findUser(Ljava.lang.String),源头为消费端的应用名称。
    2. 定义两个资源,这里与消费端相同,就不做重复解读。

    关于这个 ContextUtil 的 entry 方法非常关键,因为 Sentinel 中数据节点的统计是以 ContextName 为维度的。

    例如想对一个应用所有的操作 redis 操作统一设置为一个资源名,redisOpsResource,即想控制该应用整体的 redis 操作 tps,其场景如下:
    在这里插入图片描述
    例如初衷设计为 opsReisTotal 的整个 tps 为 500,例如从UserService#findser链路的访问 redis tps 为 400,而从 Order#createOrder 链路访问 redis tps 为 400,此时 redis 的整体 tps 已经达到了 800 tps,但你会发现并不会触发限流,因为对资源 RredisOpResource 的调用信息统计是以 context name 为维度的,不同的 context name 互不影响,从而无法做到全局控制。

    3、总结

    本文结合 Sentinel 官方对于 Dubbo 提供的适配器并加以理解,提出了如下观点,欢迎大家留言探讨,相互交流,共同进步。

    1. 限流规则根据不同的使用场景可以在客户端、服务端配置。
    2. 熔断规则通常在服务调用方配置即可。
    3. Sentinel 目前的熔断还实现的比较简单,无法解决集群中因为某一个节点的访问慢而触发熔断,并使服务稳定的能力。
    4. Sentienl 的实时统计是以调用上下文(Context Name),即 ContextUtil.entry 方法定义的上下文为维度的,这点非常关键,不然容易踩坑。

    好了,本文就介绍到这里了,您的点赞是对我持续输出高质量文章最大的鼓励。

    欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
    1、源码分析RocketMQ专栏(40篇+)
    2、源码分析Sentinel专栏(12篇+)
    3、源码分析Dubbo专栏(28篇+)
    4、源码分析Mybatis专栏
    5、源码分析Netty专栏(18篇+)
    6、源码分析JUC专栏
    7、源码分析Elasticjob专栏
    8、Elasticsearch专栏(20篇+)
    9、源码分析MyCat专栏

    展开全文
  • 本例中的电路是在电容的充电路径中插入一个限流电阻。它可探测出电容何时充电到一个阈值电压。然后,它使用一只TRIAC(三端交流开关)将电阻短路。监控电容电压比监控输入电流更好,从而避免在可能造成浪涌限流的...
  • Sentinel 触发限流的实现类为 FlowSlot。我们再来简单思考一下,要实现触发限流,至少需要完成如下几件事情: 收集实时调用信息。 设置触发限流规则 根据限流规则与调用信息来决定是否对请求进行限流等。 如何收集...

    Sentinel 触发限流的实现类为 FlowSlot。我们再来简单思考一下,要实现触发限流,至少需要完成如下几件事情:

    • 收集实时调用信息。
    • 设置触发限流规则
    • 根据限流规则与调用信息来决定是否对请求进行限流等。

    如何收集实时调用信息在前面的文章中已详细介绍,请带着上述问题开始本节的探讨。

    1、初始 FlowSlot

    我们先从 FlotSlot 类的注释来简单认识一下流量控制相关的内容。

    • 根据已(NodeSelectorSlot、ClusterNodeBuilderSlot 和 StatisticSlot)收集的运行时统计信息,FlowSlot将使用预先设置的规则来决定是否应阻止传入请求。
    • SphU.entry(resourceName)调用时,如果有任意一条规则被触发则会抛出 FlowException 异常,应用程序可捕捉该异常对业务进行定制化处理。
    • 每一条流控规则(FlowRule)都包含三个要素:流控类别、基于调用链的流控制策略、限流后的处理行为(参考FlowRule相关的注释)。
      • grade 流量控制的阈值类型
        可选值:QPS(基于QPS限流策略)、并发线程数。
      • strategy 基于调用链的流控制策略
        可选值:STRATEGY_DIRECT(根据调用方限流策略)、STRATEGY_RELATE(关联流量限
        流策略)、STRATEGY_CHAIN(根据调用链入口限流策略)
      • controlBehavior 流量控制后的采取的行为
        CONTROL_BEHAVIOR_DEFAULT(直接拒绝)、CONTROL_BEHAVIOR_WARM_UP(预热)、CONTROL_BEHAVIOR_RATE_LIMITER(匀速排队)、
        CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER(预热与匀速排队)。

    2、FlowSlot 详解

    2.1 FlowSlot 类图

    在这里插入图片描述
    FlowSlot 的类图非常简单,内部持有一个成员变量,FlowRuleChecker,用来判断是否满足流控触发条件。

    在继续探讨 Sentinel 限流之前,我们先来了解一下 FlowRule,即认识一下 Sentienl 流控规则主要包含哪些配置项,为后续的流程做一个消息的准备。

    2.2 FlowRule 配置项

    FlowRule 的类体系如图所示:
    在这里插入图片描述
    其属性的含义如下:

    • String resource
      资源的名称。
    • String limitApp
      需要限制的调用来源,对应【新增流控规则界面】的针对来源。
    • int grade
      流量控制的阈值类型,目前支持 QPS 与 并发线程数,对应 【新增流控规则界面】的阔值类型。
    • int strategy
      基于调用链的流量控制策略,对应【新增流控规则界面】的流控模式,其可选取值在本文开头部分有详细介绍。
    • String refResource
      关联资源或入口资源,当流控模式为关联或链路时配置的关联资源或入口资源,对应【新增流控规则界面】的【入口资源】
    • int controlBehavior
      流量控制后的采取的行为,其可选取值在本文开头部分有详细介绍,对应【新增流控规则界面】的流控效果。
    • int warmUpPeriodSec
      预热时间,如果 controlBehavior 设置为预热(warm up)时,可以配置其预热时间,在【新增流控规则界面】中选择 warm up 类型后,会增加一行,供用户配置,默认值 10s。
    • int maxQueueingTimeMs
      最大超时时间,如果 controlBehavior 设置为排队等待时,等待的最大超时时间,默认为500ms。
    • boolean clusterMode
      是否是集群限流模式,对应【新增流控规则界面】的是否集群。
    • ClusterFlowConfig clusterConfig
      集群扩容相关配置,集群限流将在后续文章中重点介绍。

    在 sentinel-dashboard 的配置界面如下图所示:
    在这里插入图片描述

    2.3 FlowSlot#entry 流程详解

    FlowSlot#entry

    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                          boolean prioritized, Object... args) throws Throwable {   // @1
        checkFlow(resourceWrapper, context, node, count, prioritized);       // @2
        fireEntry(context, resourceWrapper, node, count, prioritized, args);  // @3
    }
    

    代码@1:首先来解释一下该方法的参数:

    • Context context
      当前 Sentinel 调用的上下文。
    • ResourceWrapper resourceWrapper
      当前访问的资源。
    • DefaultNode node
      当前上下文环境对应的节点。
    • int count
      本次调用需要消耗的“令牌”个数
    • boolean prioritized
      是否是高优先级。
    • Object… args
      额外参数。

    代码@2:调用 checkFlow ,根据配置的限流规则,结合实时统计信息,判断是否满足流控条件,如果满足,则触发流控,稍后会详细探讨该方法的实现原理。

    代码@3:调用 fireEntry 继续沿着 slot 链进行传播。

    FlowSlot 的 checkFlow 方法在内部就是直接调用 FlowRuleChecker 的 checkFlow 方法,故我们将目光放到 FlowRuleChecker 中。

    2.4 FlowRuleChecker checkFlow 方法详解

    FlowRuleChecker#checkFlow

    public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                              Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
        if (ruleProvider == null || resource == null) { 
            return;
        }
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());   // @1
        if (rules != null) {
            for (FlowRule rule : rules) {
                if (!canPassCheck(rule, context, node, count, prioritized)) {            // @2
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
    }
    

    代码@1:通过限流规则提供器获取与该资源相关的流控规则列表。

    代码@2:然后遍历流控规则列表,通过调用 canPassCheck 方法来判断是否满足该规则设置的条件,如果满足流控规则,则抛出 FlowException,即只需要满足一个即结束校验。

    接下来继续查看 canPassCheck 方法。

    2.4.1 FlowRuleChecker canPassCheck 详解
    public boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, 
    				int acquireCount, boolean prioritized) {
        String limitApp = rule.getLimitApp(); 
        if (limitApp == null) {    // @1
            return true;
        }
        if (rule.isClusterMode()) {  // @2
            return passClusterCheck(rule, context, node, acquireCount, prioritized);  
        }
        return passLocalCheck(rule, context, node, acquireCount, prioritized);     
    }
    

    代码@1:如果限流规则没有配置针对来源,则直接默认通过,该值在配置时,默认为 default,即对所有调用发起方都生效。

    代码@2:如果是集群限流模式,则调用 passClusterCheck,非集群限流模式则调用 passLocalCheck 方法,本文重点讲述单节点限流,集群限流模式将在后续文章中详细探讨。

    FlowRuleChecker#passLocalCheck

    private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
            boolean prioritized) {
        Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);    // @1
        if (selectedNode == null) {
            return true;
        }
        return rule.getRater().canPass(selectedNode, acquireCount, prioritized);    // @2
    }
    

    代码@1:首先根据流控模式(strategy)选择一个合适的 Node,看到这,大家可以思考一下,这一步骤的目的,如果为空,则直接返回 true,表示放行。

    代码@2:调用 FlowRule 内部持有的流量控制器来判断是否符合流控规则,最终调用的是 TrafficShapingController canPass 方法。

    那我们接下来分别对上述两个方法进行详细展开。

    2.4.1.1 selectNodeByRequesterAndStrategy
    FlowRuleChecker#selectNodeByRequesterAndStrategy
    static Node selectNodeByRequesterAndStrategy(FlowRule rule, Context context, DefaultNode node) {
        String limitApp = rule.getLimitApp();
        int strategy = rule.getStrategy();
        String origin = context.getOrigin();   // @1
        if (limitApp.equals(origin) && filterOrigin(origin)) {    // @2
            if (strategy == RuleConstant.STRATEGY_DIRECT) {
                // Matches limit origin, return origin statistic node.
                return context.getOriginNode();
            }
            return selectReferenceNode(rule, context, node);
        } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {  // @3
            if (strategy == RuleConstant.STRATEGY_DIRECT) {
                // Return the cluster node.
                return node.getClusterNode();
            }
            return selectReferenceNode(rule, context, node);
        } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
                && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {    // @4
            if (strategy == RuleConstant.STRATEGY_DIRECT) {
                return context.getOriginNode();
            }
            return selectReferenceNode(rule, context, node);
        }
        return null;
    }
    

    在介绍该方法之前,先回答上文提到一个问题,我们知道,要判断是否满足了限流规则所配置的条件,一个重要的点就是要拿到当前的实时统计信息,通过上面介绍限流规则时提到 Sentinel 目前支持3种流控模式(直接、关联、链路),针对模式的不同,选择的实时统计数据的逻辑就应该不同,即该方法主要是根据流控策略找到对应的实时统计信息(Node)。

    代码@1:首先先介绍几个局部变量的含义:

    • String limitApp
      该条限流规则针对的调用方。
    • int strategy
      该条限流规则的流控策略。
    • String origin
      本次请求的调用方,从当前上下文环境中获取,例如 dubbo 服务提供者,原始调用方为 dubbo 服务提供者的 application。

    代码@2:如果限流规则配置的针对的调用方与当前请求实际调用来源匹配(并且不是 default、other)时的处理逻辑,其实现的要点:

    • 如果流控模式为 RuleConstant.STRATEGY_DIRECT(直接),则从 context 中获取源调用方所代表的 Node。
    • 如果流控模式为 RuleConstant.STRATEGY_RELATE(关联),则从集群环境中获取对应关联资源所代表的 Node,通过(ClusterBuilderSlot会收集每一个资源的实时统计信息,子集群限流时详细介绍)
    • 如果流控模式为 RuleConstant.STRATEGY_CHAIN(调用链),则判断当前调用上下文的入口资源与规则配置的是否一样,如果是,则返回入口资源对应的 Node,否则返回 null,注意:返回空则该条流控规则直接通过。【这部分代码,对应代码中的 selectReferenceNode 方法】

    代码@3:如果流控规则针对的调用方(limitApp) 配置的为 default,表示对所有的调用源都生效,其获取实时统计节点(Node)的处理逻辑为:

    • 如果流控模式为 RuleConstant.STRATEGY_DIRECT,则直接获取本次调用上下文环境对应的节点的ClusterNode。
    • 如果是其他流控模式,与代码@2的获取逻辑一样,都是调用 selectReferenceNode 进行获取。

    代码@4:如果流控规则针对的调用方为(other),此时需要判断是否有针对当前的流控规则,只要存在,则这条规则对当前资源“失效”,如果针对该资源没有配置其他额外的流控规则,则获取实时统计节点(Node)的处理逻辑为:

    • 如果流控模式为 RuleConstant.STRATEGY_DIRECT(直接),则从 context 中获取源调用方所代表的 Node。
    • 如果是其他流控模式,与代码@2的获取逻辑一样,都是调用 selectReferenceNode 进行获取。

    从这里可以看出,流控规则针对调用方如果设置为 other,表示针对没有配置流控规则的资源。

    根据流控策略选择合适的 Node 的逻辑就介绍到这里,如果没有选择到合适的 Node,则针对该流控规则,默认放行。

    2.4.1.2 TrafficShapingController canPass

    经过上一个步骤获取到对应的实时统计数据,接下来就是根据数据与流控规则,是否匹配。Sentinel 中用于实现流控规则的匹配其类体系如图所示:
    在这里插入图片描述
    由于篇幅的关系,本节只会以 DefaultController 来介绍其实现原理,对应【流控模式:快速失败】,由于篇幅的关系,其他两种流控模式将在下文详细探讨。
    DefaultController#canPass

    public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        int curCount = avgUsedTokens(node);     // @1
        if (curCount + acquireCount > count) {   // @2
            if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {   // @3
                long currentTime;
                long waitInMs;
                currentTime = TimeUtil.currentTimeMillis();
                waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);   // @4
                if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {             // @5
                    node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                    node.addOccupiedPass(acquireCount);
                    sleep(waitInMs);                                                                                  // @6
                    // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
                    throw new PriorityWaitException(waitInMs);   // @7
                }
            }
            return false;     // @8
        }
        return true;       // @9
    }
    

    代码@1:首先先解释一下两个局部变量的含义:

    • int curCount
      当前已消耗的令牌数量,即当前时间窗口内已创建的线程数量(FLOW_GRADE_THREAD) 或已通过的请求个数(FLOW_GRADE_QPS)。
    • double count
      流控规则中配置的阔值(即一个时间窗口中总的令牌个数)

    代码@2:如果当前请求的令牌数加上已消耗的令牌数之和小于总令牌数,则直接返回true,表示通过,见代码@9;如果当前时间窗口剩余令牌数小于需要申请的令牌数,则需要根据是否有优先级进行不同的处理。

    • 如果该请求存在优先级,即 prioritized 设置为 true,并且流控类型为基于QPS进行限流,则进入相关的处理逻辑,见代码@3~@8。
    • 否则直接返回 false,最终会直接抛出 FlowException,即快速失败,应用方可以捕捉该异常,对其业务进行容错处理。

    代码@4:尝试抢占下一个滑动窗口的令牌,并返回该时间窗口所剩余的时间,如果获取失败,则返回 OccupyTimeoutProperty.getOccupyTimeout() 值,该返回值的作用就是当前申请资源的线程将 sleep(阻塞)的时间。

    代码@5:如果 waitInMs 小于抢占的最大超时时间,则在下一个时间窗口中增加对应令牌数,并且线程将sleep,见代码@6。

    代码@7:这里不是很明白为什么等待 waitMs 之后,还需要抛出 PriorityWaitException,那这个prioritized 机制、可抢占下一个时间窗口的令牌有什么意义呢?应该是一个BUG吧。

    3、总结

    整个 FlowSlot 限流规则就介绍到这里了,为了更加直观的认识其限流的流程,下面给出一张流程图来对上面的源码分析进行一个总结。
    在这里插入图片描述

    该篇注重理论与实践相结合,在进行源码解读之前先从流控规则配置界面入手,代入感比较强,文章再提供一张流程图。

    整个限流部分目前还有所欠缺的两个部分:
    1、流程规则的存储与加载。
    2、其他几种流控后行为(预热、匀速排队等实现原理)

    该部分内容将在后续文章中详细介绍,本文疑似发现一个BUG,也请大家一起交流、探讨。
    在分析 DefaultController canPass 方法时,prioritized 为 true 时,执行 sleep 方法唤醒后不管三七二十一,直接抛出 PriorityWaitException 这是要起到一个什么作用呢?

    点赞是一种美德,麻烦帮忙点个赞,谢谢


    欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
    1、源码分析RocketMQ专栏(40篇+)
    2、源码分析Sentinel专栏(12篇+)
    3、源码分析Dubbo专栏(28篇+)
    4、源码分析Mybatis专栏
    5、源码分析Netty专栏(18篇+)
    6、源码分析JUC专栏
    7、源码分析Elasticjob专栏
    8、Elasticsearch专栏(20篇+)
    9、源码分析MyCat专栏

    展开全文
  • RPC实现原理之核心技术-限流熔断

    万次阅读 2021-03-11 20:49:05
    为什么要进行限流? RPC 是解决分布式系统架构通讯的一大利器,而分布式系统设计需要面临高并发问题。在这样的情况下,我们提供的每个服务节点都可能由于访问量过大而引起一系列问题,比如业务处理耗时过长、CPU 飚...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 50,246
精华内容 20,098
热门标签
关键字:

交流限流器