背景
限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自身系统和下游系统不被巨型流量冲垮等。在这种环境下,单个服务可能会接收到大量的请求,如果没有适当的流量控制机制,系统很容易因为资源被过度消耗而变得不稳定甚至崩溃。
重要性
在微服务的时代,服务之间调用在正常不过了,而且服务之间可能也是有不同小组负责,生产上每个小组都应该为自己负责的服务负责,所以评估自己系统的流量上限,然后制定一定的限流规则很重要。
- 防止服务过载:限流可以防止因突然增加的请求量导致服务器过载,确保系统对内存、CPU等资源的合理分配。
- 提高系统稳定性:通过限制处理请求的速率,限流帮助维持系统的稳定性,避免因资源竞争或服务雪崩效应导致的系统崩溃。
- 保证服务质量(QoS):限流确保系统能够在高负载情况下依然能够处理关键任务,对不同类型的流量进行优先级排序,保证重要请求的快速响应。
- 遵守法规要求:在某些行业,如金融服务或医疗信息系统,限流还可以帮助企业遵守法律法规对数据处理和服务可用性的要求。
限流算法
- 计数器算法(固定窗口计数器):在固定的时间窗口内计数,超过限制则拒绝服务。
- 滑动窗口日志:更精细地控制,允许在滑动时间窗口内动态调整流量。
- 令牌桶算法:以固定速率添加令牌到桶中,请求必须消耗令牌才能被处理,适合处理突发流量。
- 漏桶算法:请求以恒定的速率被处理,可以平滑流量峰值。
1、计数器算法
主要用来限制一定时间内的总并发数,计数器限流只要一定时间内的总请求数超过设定的阀值则进行限流,是一种简单粗暴的总数量限流,而不是平均速率限流。比如数据库连接池、线程池、秒杀的并发数;
如图:控制一个小时只允许100个请求
如图:控制一个小时只允许100个请求
优点:实现简单
缺点:有一个十分致命的问题,那就是临界问题。固定时间的两个临界点(0:59-1:01),瞬间爆发100,会导致2分钟内爆发200请求,导致请求限制不准确,给服务器造成预期之外的压力导致服务器压垮。
2、滑动窗口
滑动窗口为固定窗口的改良版,将一个窗口分为若干个等份的小窗口,每次仅滑动一小块的时间。每个小窗口对应不同的时间点,拥有独立的计数器,当请求的时间点大于当前窗口的最大时间点时,则将窗口向前平移一个小窗口
解决了固定窗口在窗口切换时会受到两倍于阈值数量的请求。在滑动窗口算法中,窗口的起止时间是动态的,窗口的大小固定。这种算法能够较好地处理窗口边界问题,
优点:减少了临界值带来的并发超过阈值的问题。
缺点:实现相对复杂,需要记录每个请求的时间戳,需要根据实际业务使用场景情况划分更多合理的小快窗口,窗口过密统计负责,窗口粒度过粗就不能很好的解决问题。
3、漏桶算法
3.1 实现原理
漏桶算法相对前面的计数算法更加柔性,它的原理也很简单,它是一种恒定速率的限流算法,不管请求量是多少,服务端的处理效率是恒定的。漏桶限流算法是一种常用的流量整形(Traffic Shaping)和流量控制(Traffic Policing)的算法,它可以有效地控制数据的传输速率以及防止网络拥塞。
漏桶是一个很形象的比喻,外部请求就像是水一样不断注入水桶中,而水桶已经设置好了最大出水速率,漏桶会以这个速率匀速放行请求,而当水超过桶的最大容量后则被丢弃。不管上面的水流速度有多块,漏桶水滴的流出速度始终保持不变。
3.2 核心步骤
a.一个固定容量的漏桶,按照固定速率出水(处理请求);
b.当流入水(请求数量)的速度过大会直接溢出(请求数量超过限制则直接拒绝)。
c.桶里的水(请求)不够则无法出水(桶内没有请求则不处理)
优点:是能够以固定的速率去控制流量,稳定性比较好。
缺点:
- 就是无法应对突发流量的来袭,以及处理请求会有延迟
- 可能会丢失数据:如果入口流量过大,超过了桶的容量,那么就需要丢弃部分请求。
- 不适合速率变化大的场景:如果速率变化大,或者需要动态调整速率,那么漏桶算法就无法满足需求。
4、令牌桶算法
令牌桶是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,填满了就丢弃令牌,请求是否被处理要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求。在流量低峰的时候,令牌桶会出现堆积,因此当出现瞬时高峰的时候,有足够多的令牌可以获取,令牌桶允许一定程度突发流量,只要有令牌就可以处理,支持一次拿多个令牌。
优点:令牌桶算法是对漏斗算法的一种改进,除了能够起到限流的作用外,还允许一定程度的流量突发。
缺点:
1、首次上线需要预热,刚上线可能会出现由于此时桶中还没有令牌,而导致请求被误杀的情况;2、网络过载:如果令牌产生的速度过剩时,大量的突发流量,这可能会使网络或服务过载。
总结
上面我们已经了解了每个限流算法的的特点,并且类比了一些应用场景。下面是各种限流算法的总结和推荐的应用场景。
固定窗口计数器
特点:在固定时间窗口内计数,简单易实现。但在窗口切换时可能会出现瞬间的请求高峰。
推荐场景:适用于请求量相对平稳,对瞬间高峰要求不严格的场景。
滑动窗口
特点:通过记录请求的时间戳在滑动的时间窗口内动态调整流量,可以更平滑地处理请求,减少瞬间峰值的影响。
推荐场景:适用于需要较为精细流量控制的场景,如电商秒杀、票务系统等高并发场景。
令牌桶算法
特点:以固定速率添加令牌到桶中,允许一定程度的突发流量。适合处理突发流量,因为它允许在令牌积累时处理突增的请求。
推荐场景:适用于需要应对突发流量的服务,如API网关、大数据处理服务等。
漏桶算法
特点:请求以恒定的速率被处理,可以平滑流量峰值,但对突发流量的响应较慢。
推荐场景:适用于需要严格控制请求处理速率的场景,如视频流服务、实时数据处理等。
综上所述,选择合适的限流算法需要考虑服务的具体需求,如是否需要应对突发流量、是否需要平滑处理高峰流量等因素。每种算法都有其适用的场景,合理的选择可以有效提升服务的稳定性和用户体验。
限流组件介绍
1、Guava LimitRate
基于google 的 Guava LimitRate【令牌桶算法】
返回类型 | 方法和描述 | 样例 |
static RateLimiter | create(double permitsPerSecond) 根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询) | RateLimiter r = RateLimiter.create(5); 使用RateLimiter的静态方法创建一个限流器,设置每秒放置的令牌数为5个。返回的RateLimiter对象可以保证1秒内不会给超过5个令牌,并且以固定速率进行放置,达到平滑输出的效果。 |
static RateLimiter | create(double permitsPerSecond,Long warmupPeriod,TimeUnti unit) 根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少请求量),在这段预热时间内,RateLimiter每秒分配许可数会平稳的增长直到预热期结束是达到其最大速率。(只要存在足够请求数来时其饱和) | RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS); RateLimiter的SmoothWarmingUp是带有预热期的平滑限流,它启动后会有一段预热期,逐步将分发频率提升到配置的速率。 创建一个平均分发令牌速率为2,预热期为3秒。由于设置了预热时间是3秒,令牌桶一开始并不会0.5秒发一个令牌,而是形成一个平滑线性下降的坡度,频率越来越高,在3秒钟之内达到原本设置的频率,以后就以固定的频率输出 |
double | acquire() 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求。 | |
double | acquire(int permits) 从RateLimiter获取指定数量许可,该方法会被阻塞直到获取到请求。 | |
double | getRate() 返回RateLimiter配置中的稳定速率,该速率单位是每秒多少许可数 | |
void | setRate(double pemitsPerSecond) 更新RateLimiter的稳定速率,参数permitsPerSecond由构造RateLimiter的工厂方法提供。 | |
boolean | tryAcquire() 从RateLimiter获取许可,如果该许可可以在无延迟下的情况下立即获取的话返回true,否则返回false。 | |
boolean | tryAcquire(int permits) 从RateLimiter中获取指定数量许可,如果该 许可数可以在无延迟的情况下立即获取得到返回true,否则返回false | |
boolean | tryAcquire(int permits,long timeout,TimeUnit unit) 从RateLimiter中获取指定数量许可,如果该许可可以在不超过timeout的时间内获取得到的话返回true,如果无法在timeout时间内获取到许可则返回false。 简述:在指定时间(timeout)内获取指定数量(permits)许可。 | |
boolean | tryAcquire(long timeout,TimeUnit unit) 从RateLimiter中获取一个许可,如果该许可可以在不超过timeout的时间内获取得到的话返回true,如果无法在timeout时间内获取到许可则返回false 简述:在指定时间内获取一个许可。 |
2、redisson
基于Redis的分布式限流器RateLimiter可以用来在分布式环境下现在请求方的调用频率。既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。
RateLimter主要作用就是可以限制调用接口的次数。主要原理就是调用接口之前,需要拥有指定个令牌。限流器每秒会产生X个令牌放入令牌桶,调用接口需要去令牌桶里面拿令牌。如果令牌被其它请求拿完了,那么自然而然,当前请求就调用不到指定的接口。
一、样例
1.1初始化
// 初始化
RRateLimiter rateLimiter = redisson.getRateLimiter("testRate");
// 最大流速 = 每10秒钟产生1个令牌
rateLimiter.trySetRate(RateType.OVERALL, 1, 10, RateIntervalUnit.SECONDS);
trySetRate 用于设置限流参数。其中 RateType 包含 OVERALL 和 PER_CLIENT 两个枚举常量,分别表示全局限流和单机限流。后面三个参数表明了令牌的生成速率,即每 rateInterval 生成 rate 个令牌,rateIntervalUnit 为 rateInterval 的时间单位。
1.2 获取令牌
void acquire(long permits);
boolean tryAcquire(long permits, long timeout, TimeUnit unit);
acquire 和 tryAcquire 均可用于获取指定数量的令牌,不过 acquire 会阻塞等待,而 tryAcquire 会等待 timeout 时间,如果仍然没有获得指定数量的令牌直接返回 false。
二、实现原理
Redisson 的 RRateLimiter 基于令牌桶实现,令牌桶的主要特点如下:
令牌以固定速率生成。
生成的令牌放入令牌桶中存放,如果令牌桶满了则多余的令牌会直接丢弃,当请求到达时,会尝试从令牌桶中取令牌,取到了令牌的请求可以执行。
如果桶空了,那么尝试取令牌的请求会被直接丢弃。
trySetRate方法跟进去底层实现如下:
@Override
public RFuture<Boolean> trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);"
+ "redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);"
+ "return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);",
Collections.<Object>singletonList(getName()), rate, unit.toMillis(rateInterval), type.ordinal());
}
比如下面这段代码,5秒中产生3个令牌,并且所有实例共享(RateType.OVERALL所有实例共享、RateType.CLIENT单实例端共享)
trySetRate(RateType.OVERALL, 3, 5, RateIntervalUnit.SECONDS);
那么redis中就会设置3个参数:
hsetnx,key,rate,3
hsetnx,key,interval,5
hsetnx,key,type,0
接着看tryAcquire(1)方法:底层源码如下
private <T> RFuture<T> tryAcquireAsync(RedisCommand<T> command, Long value) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"local rate = redis.call('hget', KEYS[1], 'rate');" //1
+ "local interval = redis.call('hget', KEYS[1], 'interval');" //2
+ "local type = redis.call('hget', KEYS[1], 'type');" //3
+ "assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')" //4
+ "local valueName = KEYS[2];" //5
+ "if type == 1 then "
+ "valueName = KEYS[3];" //6
+ "end;"
+ "local currentValue = redis.call('get', valueName); " //7
+ "if currentValue ~= false then "
+ "if tonumber(currentValue) < tonumber(ARGV[1]) then " //8
+ "return redis.call('pttl', valueName); "
+ "else "
+ "redis.call('decrby', valueName, ARGV[1]); " //9
+ "return nil; "
+ "end; "
+ "else " //10
+ "redis.call('set', valueName, rate, 'px', interval); "
+ "redis.call('decrby', valueName, ARGV[1]); "
+ "return nil; "
+ "end;",
Arrays.<Object>asList(getName(), getValueName(), getClientValueName()),
value, commandExecutor.getConnectionManager().getId().toString());
}
接着看第7标准行,获取valueName对应的值currentValue;首次获取肯定为空,那么看第10标准行else的逻辑
set valueName 3 px 5,设置key=valueName value=3 过期时间为5秒
decrby valueName 1,将上面valueName的值减1
那么如果第二次访问,第7标注行返回的值存在,将会走第8标注行,紧接着走如下判断
如果当前valueName的值也就是3,小于要获得的令牌数量(tryAcquire方法中的入参),那么说明当前时间内(key的有效期5秒内),令牌的数量已经被用完,返回pttl(key的剩余过期时间);反之说明桶中有足够的令牌,获取之后将会把桶中的令牌数量减1,至此结束。
3、hystrix
Hystrix 的限流主要是通过线程池隔离和信号量来实现的,它不直接提供限流功能。Hystrix的核心在于熔断和隔离,虽然通过并发策略可以间接实现某种形式的限流
1、线程池隔离模式
使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)。
2、信号量隔离模式
使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)。
线程池隔离与信号量隔离的最大区别在于发送请求的线程,信号量是采用调用方法的线程,而线程池则是用池内的线程去发送请求
1)线程池隔离的设置
//线程池隔离的设置
@HystrixCommand(
groupKey="test-provider",
threadPoolKey="test-provider",
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "20"),//线程池大小
@HystrixProperty(name = "maximumSize", value = "30"),//最大线程池大小
@HystrixProperty(name = "maxQueueSize", value = "20"),//最大队列长度
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2")//线程存活时间
},commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy",value = "THREAD")
2)信号量隔离的设置
//信号量隔离的设置
@HystrixCommand(
//用来设置降级方法
fallbackMethod = "myTestFallbackMethod",
commandProperties = {
//进行熔断配置
//条件1,设置在滚动时间窗口中,断路器的最小请求数(没有达到不会熔断)。默认20。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold" ,value = "10"),
//条件2,设置断路器打开的错误百分比。在滚动时间内,在请求数量超过requestVolumeThreshold的值,且错误请求数的百分比超过这个比例,断路器就为打开状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage" ,value = "30"),
//条件3,设置滚动时间窗的长度,单位毫秒。这个时间窗口就是断路器收集信息的持续时间。断路器在收集指标信息的时会根据这个时间窗口把这个窗口拆分成多个桶,每个桶代表一段时间的指标,默认10000.
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds" ,value = "10000"),
//设置当断路器打开之后的休眠时间,休眠时间结束后断路器为半开状态,断路器能接受请求,如果请求失败又重新回到打开状态,如果请求成功又回到关闭状态
//单位是毫秒
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds" ,value = "3000"),
//配置信号量隔离
//配置信号量的数值
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "100"),
//选择策略为信号量隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"),
//设置HystrixCommand执行的超时时间,单位毫秒
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000000000")
}
)
Hystrix整个工作流程如下:
- 构造一个 HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数;
- 执行命令,Hystrix提供了4种执行命令的方法,后面详述;
- 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动;
- 判断熔断器是否打开,如果打开,跳到第8步;
- 判断线程池/队列/信号量是否已满,已满则跳到第8步;
- 执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
- 统计熔断器监控指标;
- 走Fallback备用逻辑
- 返回请求响应
从流程图上可知道,第5步线程池/队列/信号量已满时,还会执行第7步逻辑,更新熔断器统计信息,而第6步无论成功与否,都会更新熔断器统计信息。
4、Sentinel
Sentinel与Hystrix的区别
Items | Sentinel | Hystrix | remark |
隔离策略 | 信号量隔离(并发线程数限流)(模拟信号量) | 线程池隔离/信号量隔离 | Sentinel不创建线程依赖tomcat或jetty容器的线程池,存在的问题就是运行容器的线程数量限制了sentinel设置值的上限可能设置不准。比如tomcat线程池为10,sentinel设置100是没有意义的,同时隔离性不好hystrix使用自己创建的线程池,隔离性会更好 |
熔断降级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 | 快速失败的本质功能 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基于 RxJava) | |
动态规则配置 | 支持多种数据源 | 支持多种数据源 | |
扩展性 | 多个扩展点 | 插件的形式 | |
基于注解的支持 | 支持 | 支持 | |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持(并发线程数或信号量大小) | 快速失败的本质功能 |
流量整形 | 支持预热模式、匀速器模式、预热排队模式 | 不支持(排队) | 支持排队好吧 |
系统自适应保护 | 支持(仅对linux生效) | 不支持 | 所谓的自适应就是设置一个服务器最大允许处理量的阈值。(有比没有强,但是要知道最大负载量是多少。) |
控制台 | 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 | 简单的监控查看接近实时数据 | 控制台是非常有竞争力的功能,因为能集中配置限制数据更方便,但是展示数据和实时性没有hystrix直观。 |
配置持久化 | ZooKeeper, Apollo, Nacos | Git/svn/本地文件 | Sentinel客户端采用直接链接持久化存储,应用客户端引用了更多的依赖,同样的存储链接可能有多个配置 |
动态配置 | 支持 | 支持 | hystrix可能需要手动触发,sentinel增加了额外的端口进行配置文件控制,应该也支持spring boot动态配置 |
黑白名单 | 支持 | 不支持 | 个人觉得这个功能用的不是很多 |
springcloud集成 | 高 | 非常高 | Spring boot使用hystrix会更方便 |
整体优势 | 集中配置设置及监控+更细的控制规则 | 漂亮的界面+接近实时的统计结果 | 集中配置可能更有吸引力,但是配置值是多少以及让谁控制依然是很头疼的事情。运维控制可能不知道哪个应该优先哪个不优先,应该调整到多大。什么时候更适合使用sentinel?个人认为docker容器化部署之后sentinel可能更会发挥作用,但是会有另外的竞品出现做选型。 |
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合Dashboard 可以取得最好的效果。
基本原理
在 Sentinel 体系里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:
1、NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
2、ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT、QPS 以及 Thread Count 等等,这些信息将用作为多维度限流,降级的依据;
3、StatisticSlot 则用于记录、统计不同纬度的 Runtime 指标监控信息;
4、FlowSlot 则用于根据预设的限流规则以及前面 Slot 统计的状态,来进行流量控制;
5、AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
6、DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
7、SystemSlot 则通过系统的状态,例如 Load 等,来控制总的入口流量。
public static void main(String[] args) {
initFlowRules(); //初始化一个规则
while(true){
Entry entry=null;
try{
entry= SphU.entry(resource); //它做了什么
System.out.println("Hello Word");
}catch (BlockException e){//如果被限流了,那么会抛出这个异常
e.printStackTrace();
}finally {
if(entry!=null){
entry.exit();// 释放
}
}
}
}
一般Sentinel限流都是通过:
这个Entry相当于是获取到了一个令牌,如果能够获取到这个令牌,表示可以通过,能够访问资源。
Entry entry = SphU.entry('entryName');
SphU.entry() 的参数描述:
参数名 | 类型 | 解释 | 默认值 |
entryType | EntryType | 资源调用的流量类型,是入口流量(EntryType.IN)还是出口流量(EntryType.OUT),注意系统规则只对 IN 生效 | EntryType.OUT |
count | int | 本次资源调用请求的 token 数目 | 1 |
args | Object[] | 传入的参数,用于热点参数限流 | 无 |
概念
- 资源
- 资源:可以是任何东西,一个服务,服务里的方法,甚至是一段代码。
- 规则
- 规则:Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则
- 和 热点参数规则。
资源定义
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项
- value:资源名称,必需项(不能为空)
- entryType:entry 类型,可选项(默认为 EntryType.OUT)
- blockHandler / blockHandlerClass:
blockHandler 对应处理 BlockException的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- fallback /fallbackClass
fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。
- defaultFallback
规则定义
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
- resource:资源名,即限流规则的作用对象
- count: 限流阈值
- grade: 限流阈值类型(QPS 或并发线程数)
- limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
- strategy: 调用关系限流策略
- controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
1、快速失败,直接拒绝请求,返回失败
2、Warm up(预热)模式
当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。
默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
先在单机阈值10/3,3的时候,预热10秒后,慢慢将阈值升至20。刚开始刷/testWarmUP,会出现默认错误,预热时间到了后,阈值增加,没超过阈值刷新,请求正常。
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
排队等待模式
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。阈值必须设置为QPS。
这种方式主要用于处理间隔性突发的流量,例如消息队列
某瞬时来了大流量的请求, 而如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力。Sentinel的Rate Limiter模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性.
热点规则 (ParamFlowRule)
热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule):
属性 | 说明 | 默认值 |
resource | 资源名,必填 | |
count | 限流阈值,必填 | |
grade | 限流模式 | QPS 模式 |
durationInSec | 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 | 1s |
controlBehavior | 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 | 快速失败 |
maxQueueingTimeMs | 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 | 0ms |
paramIdx | 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置 | |
paramFlowItemList | 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型 | |
clusterMode | 是否是集群参数流控规则 | false |
clusterConfig | 集群流控相关配置 |
5、Nginx
现在已经是最火的负载均衡之一,在流量陡增的互联网面前,接口限流也是很有必要的,尤其是针对高并发的场景。Nginx的限流主要是两种方式:限制访问频率和限制并发连接数。
一、限制访问频率(正常流量)
Nginx中我们使用 ngx_http_limit_req_module模块来限制请求的访问频率,基于漏桶算法原理实现。接下来我们使用 nginx limit_req_zone 和 limit_req 两个指令,限制单个IP的请求处理速率。
语法:limit_req_zone key zone rate
key :定义限流对象,binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。zone:定义共享内存区来存储访问信息, myRateLimit:10m 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。
二、限制访问频率(突发流量)
按上面的配置在流量突然增大时,超出的请求将被拒绝,无法处理突发流量,那么在处理突发流量的时候,该怎么处理呢?Nginx提供了 burst 参数来解决突发流量的问题,并结合 nodelay 参数一起使用。burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数。
burst=20 nodelay表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms一个来释放。这就达到了速率稳定,但突然流量也能正常处理的效果。
三、限制并发连接数
Nginx 的 ngx_http_limit_conn_module模块提供了对资源连接数进行限制的功能,使用 limit_conn_zone 和 limit_conn 两个指令就可以了。
limit_conn perip 20:对应的key是 $binary_remote_addr,表示限制单个IP同时最多能持有20个连接。limit_conn perserver 100:对应的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。注意,只有当 request header 被后端server处理后,这个连接才进行计数。
标签:令牌,请求,处理,汇总,流量,限流,线程,组件 From: https://blog.csdn.net/hefaji/article/details/139661253