流量预警和限流方案中,比较常用的有两种。第一种滑窗模式,通过统计一段时间内的访问次数来进行控制,访问次数达到的某个峰值时进行限流。第二种为并发用户数模式,通过控制最大并发用户数,来达到流量控制的目的。下面来简单分析下两种的优缺点。
1、滑窗模式
模式分析:
在每次有访问进来时,我们判断前N个单位时间内的总访问量是否超过了设置的阈值,并对当前时间片上的请求数+1。
上图每一个格式表示一个固定的时间(比如1s),每个格子一个计数器,我们要获取前5s的请求量,就是对当前时间片i ~ i-4的时间片上计数器进行累加。
这种模式的实现的方式更加契合流控的本质意义。理解较为简单。但由于访问量的不可预见性,会发生单位时间的前半段大量请求涌入,而后半段则拒绝所有请求的情况。(通常,需要可以将单位时间切的足够的小来缓解 )其次,我们很难确定这个阈值设置在多少比较合适,只能通过经验或者模拟(如压测)来进行估计,即使是压测也很难估计的准确。集群部署中每台机器的硬件参数不同,可能导致我们需要对每台机器的阈值设置的都不尽相同。同一台机子在不同的时间点的系统压力也不一样(比如晚上还有一些任务,或其他的一些业务操作的影响),能够承受的最大阈值也不尽相同,我们无法考虑的周全。
所以滑窗模式通常适用于对某一资源的保护的需求上(或者说是承诺比较合适:我对某一接口的提供者承诺过,最高调用量不超过XX),如对db的保护,对某一服务的调用的控制上。
代码实现思路:
每一个时间片(单位时间)就是一个独立的计数器,用以数组保存。将当前时间以某种方式(比如取模)映射到数组的一项中。每次访问先对当前时间片上的计数器+1,再计算前N个时间片的访问量总合,超过阈值则限流。
/** * 滑窗的实现 * @author shimig * */ public class SlidingWindow { /* 循环队列 */ private volatile AtomicInteger[] timeSlices; /* 队列的总长度 */ private volatile int timeSliceSize; /* 每个时间片的时长 */ private volatile int timeMillisPerSlice; /* 窗口长度 */ private volatile int windowSize; /* 当前所使用的时间片位置 */ private AtomicInteger cursor = new AtomicInteger(0); public SlidingWindow(int timeMillisPerSlice, int windowSize) { this.timeMillisPerSlice = timeMillisPerSlice; this.windowSize = windowSize; // 保证存储在至少两个window this.timeSliceSize = windowSize * 2 + 1; } /** * 初始化队列,由于此初始化会申请一些内容空间,为了节省空间,延迟初始化 */ private void initTimeSlices() { if (timeSlices != null) { return; } // 在多线程的情况下,会出现多次初始化的情况,没关系 // 我们只需要保证,获取到的值一定是一个稳定的,所有这里使用先初始化,最后赋值的方法 AtomicInteger[] localTimeSlices = new AtomicInteger[timeSliceSize]; for (int i = 0; i < timeSliceSize; i++) { localTimeSlices[i] = new AtomicInteger(0); } timeSlices = localTimeSlices; } private int locationIndex() { long time = System.currentTimeMillis(); return (int) ((time / timeMillisPerSlice) % timeSliceSize); } /** * <p>对时间片计数+1,并返回窗口中所有的计数总和 * <p>该方法只要调用就一定会对某个时间片进行+1 * * @return */ public int incrementAndSum() { initTimeSlices(); int index = locationIndex(); int sum = 0; // cursor等于index,返回true // cursor不等于index,返回false,并会将cursor设置为index int oldCursor = cursor.getAndSet(index); if (oldCursor == index) { // 在当前时间片里继续+1 sum += timeSlices[index].incrementAndGet(); } else { // 可能有其他thread已经置过1,问题不大 timeSlices[index].set(1); // 清零,访问量不大时会有时间片跳跃的情况 clearBetween(oldCursor, index); // sum += 0; } for (int i = 1; i < windowSize; i++) { sum += timeSlices[(index - i + timeSliceSize) % timeSliceSize].get(); } return sum; } /** * 判断是否允许进行访问,未超过阈值的话才会对某个时间片+1 * * @param threshold * @return */ public boolean allow(int threshold) { initTimeSlices(); int index = locationIndex(); int sum = 0; // cursor不等于index,将cursor设置为index int oldCursor = cursor.getAndSet(index); if (oldCursor != index) { // 可能有其他thread已经置过1,问题不大 timeSlices[index].set(0); // 清零,访问量不大时会有时间片跳跃的情况 clearBetween(oldCursor, index); } for (int i = 1; i < windowSize; i++) { sum += timeSlices[(index - i + timeSliceSize) % timeSliceSize].get(); } // 阈值判断 if (sum <= threshold) { // 未超过阈值才+1 sum += timeSlices[index].incrementAndGet(); return true; } return false; } /** * <p>将fromIndex~toIndex之间的时间片计数都清零 * <p>极端情况下,当循环队列已经走了超过1个timeSliceSize以上,这里的清零并不能如期望的进行 * * @param fromIndex 不包含 * @param toIndex 不包含 */ private void clearBetween(int fromIndex, int toIndex) { for (int index = (fromIndex + 1) % timeSliceSize; index != toIndex; index = (index + 1) % timeSliceSize) { timeSlices[index].set(0); } } }
2、并发用户数模式
模式分析:
每次操作执行时,我们通过判断当前正在执行的访问数是否超过某个阈值在决定是否限流。
该模式看着思路比较的另类,但却有其独到之处。实际上我们限流的根本是为了保护资源,防止系统接受的请求过多,应接不暇,拖慢系统中其他接口的服务,造成雪崩。我们真正需要关心的是那些运行中的请求,而那些已经完成的请求已是过去时,不再是需要关心的了。
我们来看看其阈值的计算方式,对于一个请求来说,响应时间rt、qps是一个比较容易获取的参数,那么我们这样计算:qps/1000*rt。
此外,一个应用往往是个复杂的系统,提供的服务或者暴露的请求、资源不止一个。内部GC、定时任务的执行、其他服务访问的骤增,外部依赖方、db的抖动,抑或是代码中不经意间的一个bug。都可能导致响应时间的变化,导致系统性能容量的改变 。而这种模式,则能恰如其分的自动做出调整,当系统不适时,rt增加,会自动的对qps做出适应。
代码实现思路:
当访问开始时,我们对当前计数器(原子计数器)+1,当完成时,-1。该计数器即为当前正在执行的请求数。只需判断这个计数器是否超过阈值即可。
相关推荐
在这个“hyxtrix限流.zip”压缩包中,很可能是关于如何使用Hystrix进行接口限流的相关资料。 首先,我们需要理解Hystrix的核心功能:断路器。断路器模式是一种设计模式,当服务出现故障时,断路器会打开,阻止...
在电路设计过程中,存在两种主要的限流保护方案:直接取样和间接取样。直接取样方案通常在电流回路中取样后直接进行限流控制处理。例如,通过信号放大后产生低电压的控制电平信号,进而控制PWM电路输出,以控制功率...
常见的限流算法有两种:令牌桶算法和漏桶算法。 1. **令牌桶算法**:令牌桶算法允许突发流量,但同时设置了最大速率。系统会按照一定的速率往桶里添加令牌,只有当桶中有令牌时,请求才能被处理。令牌的数量决定了...
这两种算法都是在网络编程和并发控制中广泛使用的流量整形工具。 描述中没有提供更多的具体信息,但我们可以推测这个组件可能是为了方便Spring Boot开发者快速集成和配置限流功能。这通常包括以下几个核心特性: 1...
- API 类型分为 RoutID 和 API 分组两种。RoutID 是网关路由的唯一标识,而 API 分组则是将多个具有相同特征的路由聚合在一起的逻辑单元。 - API 名称可以是 RoutID 或者 API 分组名称,用于区分不同的流量控制...
在实现中,提供了两种限流策略:快速失败和CAS(Compare and Swap)阻塞。快速失败策略意味着当没有可用令牌时,请求将立即被拒绝,避免了额外的等待时间。而CAS阻塞策略则会让超出限流速率的请求进入等待队列,待有...
Sentinel 集群限流提供了两种规则: 1. 限流规则:限制整个集群内某个资源的总体 QPS 不超过设定的阈值。 2. 热点规则:针对可能出现热点的资源,限制其调用频率。 同时,集群限流支持两种阈值计算方式: 1. 集群...
控制器在接收到超限信号后,有两种常见的限流策略:硬限流和软限流。硬限流会立即关闭开关,切断电流流动,以防止过电流状态持续;而软限流则会逐渐降低开关的占空比,使得电流缓慢下降到安全范围内,这种方法对系统...
图3展示了一种限流电路设计方案,其中电阻Rsc与负载电流IL相连,当负载电流超过预设值时,通过晶体管S1的导通使电容器C1充电。充电完成后,电容器C1产生的电压使晶体管S2导通,进而关闭PWM驱动信号,实现过流保护。 ...
本篇文章将详细讲解如何使用Go语言和Redis来实现并发安全的限流器,同时涵盖计数器限流和滑动窗口限流两种策略。 首先,我们来理解"Go+Redis实现的并发安全限流器"的核心概念。Go语言以其高效的并发模型和丰富的库...
2. **恒定电流与限定电流模式**:这款IC支持两种驱动模式——恒定电流模式和限定电流模式。恒定电流模式保证了LED发光的一致性,而限定电流模式则允许用户根据实际需求调整电流值,以适应不同的应用环境。 3. **...
限流通常有两种策略:固定窗口限流和滑动窗口限流。固定窗口限流会在特定时间间隔内限制请求的数量,而滑动窗口限流则会连续划分多个小的时间窗口,每个窗口都有独立的请求配额。在此案例中,我们可能采用的是滑动...
规则定义主要包括资源名(resource)、限流阈值类型(grade)、阈值(count)以及一些高级属性,如限流模式(strategy)、流控策略(controlBehavior)等。 `grade` 通常可以设置为 QPS(每秒请求数量)或线程数。`...
在微服务架构中,服务治理是一项关键任务,其中包括了接口鉴权和限流等功能,以确保系统的稳定性和安全性。本文将深入探讨这两个方面所涉及的数据结构和算法。 首先,我们来看鉴权。鉴权主要是控制不同应用对微服务...
在电子电路设计与分析中,限流电路和分压电路是两种非常基础且重要的电路组成。它们各自扮演不同的角色,对电流和电压的控制起到关键性的作用。在本次的PPT学习教案中,我们将通过系统性的方式,对这两种电路及其...
本文将通过建立模型分析的方法对这两种控制方式进行比较,分析它们的限流速度和控制稳定性。 首先,我们需要了解开关电源的基本工作原理。开关电源是一个闭环的自动控制系统,其控制环节的设计是设计过程中的重要...
`LimitType` 枚举定义了两种限流策略:DROP(丢弃)和 WAIT(等待)。DROP 策略会直接拒绝超出限流速率的请求,而 WAIT 策略会让请求等待,直到有足够的令牌可用。 **配置拦截器** 在 SpringMVC 配置中,需要将...
这款驱动器具备恒流和限流两种模式,确保了对WLED的稳定驱动,并能在电源电压下降时保持均匀的亮度。 **关键特性** 1. **宽输入电压范围**:0.8V至5.5V,适应性强,适合多种电源。 2. **高效能**:高达90%的效率,...
本文主要探讨Sentinel限流、熔断降级的两种核心算法:计数器法和滑动时间窗口算法。 首先,我们来看计数器法。计数器法是最基础的限流策略,适用于简单的场景。例如,若要限制A接口1分钟内的访问次数不超过100次,...