关于两种限流模式

流量预警和限流方案中,比较常用的有两种。第一种滑窗模式,通过统计一段时间内的访问次数来进行控制,访问次数达到的某个峰值时进行限流。第二种为并发用户数模式,通过控制最大并发用户数,来达到流量控制的目的。下面来简单分析下两种的优缺点。

1、滑窗模式

模式分析:

在每次有访问进来时,我们判断前N个单位时间内的总访问量是否超过了设置的阈值,并对当前时间片上的请求数+1。

上图每一个格式表示一个固定的时间(比如1s),每个格子一个计数器,我们要获取前5s的请求量,就是对当前时间片i ~ i-4的时间片上计数器进行累加。

这种模式的实现的方式更加契合流控的本质意义。理解较为简单。但由于访问量的不可预见性,会发生单位时间的前半段大量请求涌入,而后半段则拒绝所有请求的情况。(通常,需要可以将单位时间切的足够的小来缓解

)其次,我们很难确定这个阈值设置在多少比较合适,只能通过经验或者模拟(如压测)来进行估计,即使是压测也很难估计的准确。集群部署中每台机器的硬件参
数不同,可能导致我们需要对每台机器的阈值设置的都不尽相同。同一台机子在不同的时间点的系统压力也不一样(比如晚上还有一些任务,或其他的一些业务操作
的影响),能够承受的最大阈值也不尽相同,我们无法考虑的周全。

所以滑窗模式通常适用于对某一资源的保护的需求上(或者说是承诺比较合适:我对某一接口的提供者承诺过,最高调用量不超过XX),如对db的保护,对某一服务的调用的控制上。

代码实现思路:

每一个时间片(单位时间)就是一个独立的计数器,用以数组保存。将当前时间以某种方式(比如取模)映射到数组的一项中。每次访问先对当前时间片上的计数器+1,再计算前N个时间片的访问量总合,超过阈值则限流。

Java代码  

  1. /**
  2. * 滑窗的实现
  3. * @author shimig
  4. *
  5. */
  6. public class SlidingWindow {
  7. /* 循环队列 */
  8. private volatile AtomicInteger[] timeSlices;
  9. /* 队列的总长度  */
  10. private volatile int timeSliceSize;
  11. /* 每个时间片的时长 */
  12. private volatile int timeMillisPerSlice;
  13. /* 窗口长度 */
  14. private volatile int windowSize;
  15. /* 当前所使用的时间片位置 */
  16. private AtomicInteger cursor = new AtomicInteger(0);
  17. public SlidingWindow(int timeMillisPerSlice, int windowSize) {
  18. this.timeMillisPerSlice = timeMillisPerSlice;
  19. this.windowSize = windowSize;
  20. // 保证存储在至少两个window
  21. this.timeSliceSize = windowSize * 2 + 1;
  22. }
  23. /**
  24. * 初始化队列,由于此初始化会申请一些内容空间,为了节省空间,延迟初始化
  25. */
  26. private void initTimeSlices() {
  27. if (timeSlices != null) {
  28. return;
  29. }
  30. // 在多线程的情况下,会出现多次初始化的情况,没关系
  31. // 我们只需要保证,获取到的值一定是一个稳定的,所有这里使用先初始化,最后赋值的方法
  32. AtomicInteger[] localTimeSlices = new AtomicInteger[timeSliceSize];
  33. for (int i = 0; i < timeSliceSize; i++) {
  34. localTimeSlices[i] = new AtomicInteger(0);
  35. }
  36. timeSlices = localTimeSlices;
  37. }
  38. private int locationIndex() {
  39. long time = System.currentTimeMillis();
  40. return (int) ((time / timeMillisPerSlice) % timeSliceSize);
  41. }
  42. /**
  43. * <p>对时间片计数+1,并返回窗口中所有的计数总和
  44. * <p>该方法只要调用就一定会对某个时间片进行+1
  45. *
  46. * @return
  47. */
  48. public int incrementAndSum() {
  49. initTimeSlices();
  50. int index = locationIndex();
  51. int sum = 0;
  52. // cursor等于index,返回true
  53. // cursor不等于index,返回false,并会将cursor设置为index
  54. int oldCursor = cursor.getAndSet(index);
  55. if (oldCursor == index) {
  56. // 在当前时间片里继续+1
  57. sum += timeSlices[index].incrementAndGet();
  58. } else {
  59. // 可能有其他thread已经置过1,问题不大
  60. timeSlices[index].set(1);
  61. // 清零,访问量不大时会有时间片跳跃的情况
  62. clearBetween(oldCursor, index);
  63. // sum += 0;
  64. }
  65. for (int i = 1; i < windowSize; i++) {
  66. sum += timeSlices[(index - i + timeSliceSize) % timeSliceSize].get();
  67. }
  68. return sum;
  69. }
  70. /**
  71. * 判断是否允许进行访问,未超过阈值的话才会对某个时间片+1
  72. *
  73. * @param threshold
  74. * @return
  75. */
  76. public boolean allow(int threshold) {
  77. initTimeSlices();
  78. int index = locationIndex();
  79. int sum = 0;
  80. // cursor不等于index,将cursor设置为index
  81. int oldCursor = cursor.getAndSet(index);
  82. if (oldCursor != index) {
  83. // 可能有其他thread已经置过1,问题不大
  84. timeSlices[index].set(0);
  85. // 清零,访问量不大时会有时间片跳跃的情况
  86. clearBetween(oldCursor, index);
  87. }
  88. for (int i = 1; i < windowSize; i++) {
  89. sum += timeSlices[(index - i + timeSliceSize) % timeSliceSize].get();
  90. }
  91. // 阈值判断
  92. if (sum <= threshold) {
  93. // 未超过阈值才+1
  94. sum += timeSlices[index].incrementAndGet();
  95. return true;
  96. }
  97. return false;
  98. }
  99. /**
  100. * <p>将fromIndex~toIndex之间的时间片计数都清零
  101. * <p>极端情况下,当循环队列已经走了超过1个timeSliceSize以上,这里的清零并不能如期望的进行
  102. *
  103. * @param fromIndex 不包含
  104. * @param toIndex 不包含
  105. */
  106. private void clearBetween(int fromIndex, int toIndex) {
  107. for (int index = (fromIndex + 1) % timeSliceSize; index != toIndex; index = (index + 1) % timeSliceSize) {
  108. timeSlices[index].set(0);
  109. }
  110. }
  111. }

2、并发用户数模式

模式分析:

每次操作执行时,我们通过判断当前正在执行的访问数是否超过某个阈值在决定是否限流。

该模式看着思路比较的另类,但却有其独到之处。实际上我们限流的根本是为了保护资源,防止系统接受的请求过多,应接不暇,拖慢系统中其他接口的服务,造成雪崩。我们真正需要关心的是那些运行中的请求,而那些已经完成的请求已是过去时,不再是需要关心的了。

我们来看看其阈值的计算方式,对于一个请求来说,响应时间rt、qps是一个比较容易获取的参数,那么我们这样计算:qps/1000*rt。

此外,一个应用往往是个复杂的系统,提供的服务或者暴露的请求、资源不止一个。内部GC、定时任务的执行、其他服务访问的骤增,外部依赖方、db的抖动,抑或是代码中不经意间的一个bug。都可能导致响应时间的变化,导致系统性能容量的改变 。而这种模式,则能恰如其分的自动做出调整,当系统不适时,rt增加,会自动的对qps做出适应。

代码实现思路:

当访问开始时,我们对当前计数器(原子计数器)+1,当完成时,-1。该计数器即为当前正在执行的请求数。只需判断这个计数器是否超过阈值即可。

时间: 2024-08-11 22:50:18

关于两种限流模式的相关文章

TI_DSP_SRIO - 两种SRIO操作模式

DSP SRIO协议的逻辑层定义了操作协议和相应的包格式.DSP上SRIO支持的逻辑层业务(数据发送方法)主要是直接IO/DMA(Direct IO/ Direct Memory Access)和消息传递(Message Passing). ?直接IO/DMA模式是最简单实用的传输方式,其前提是主设备知道被访问端的存储器映射.在这种模式下,主设备可以直接读写从设备的存储器.可以硬件直接实现. ?消息传递模式则类似于以太网的传输方式,它不要求主设备知道被访问设备的存储器状况.数据在被访问设备中的位

JavaScript中的两种事件流

JavaScript中的两种事件流 事件流描述的是从页面中接收事件的顺序.提出事件流概念的正是IE和Netscape,但是前者提出的是我们常用的事件冒泡流,而后者提出的是事件捕获流. 第一部分:事件冒泡 即事件开始由最具体的元素接收,然后逐级向上传播到较为不具体的节点(文档). 下面举一个简单的例子: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"&

限流模式【其他模式】

限流模式 @Slf4j public class Throttling { /** * 限流模式: * Ensure that a given client is not able to access service resources * more than the assigned limit. * 确保给定的客户端不能访问超出限制的服务资源. */ @Test public void all() throws InterruptedException { // 每秒最多可获取 5 个许可的

有关文件读取写入 和两种文件打开模式的理解

文件有两种打开模式:文本模式和二进制模式 当c语言程序对文件操作时,先以某种模式打开文件,建立一个缓存去(读写模式下两个):缓存区中有文件的控制信息,然后用I/O函数操作文件. 对于fread和fwrite函数,不管文件是以什么模式打开的,fread和fwrite只会原样复制原始数据,而其他函数可能会对原始函数进行转化. 之后我们再用应用程序打开文件,取决于应用程序对文件的解释. 先写到这,等以后更加深一步了解了再补充.

以Attention Model为例谈谈两种研究创新模式

/* 版权声明:可以任意转载,转载时请标明文章原始出处和作者信息 .*/                                                     author: 张俊林 各位观众朋友好,也许此刻您刚打开电梯--.读这一篇之前,请您最好先拜读一下本篇的前传:文本处理中的Attention Model:是什么及为什么.因为那里有些背景知识需要交代. 话接上回书,在研读AttentionModel相关文献过程中,我再次深切感受到了科研中的两种创新模式:模型创新与应用创新

两种交换机配置模式,以配置基于端口划分的VLAN为例

关于交换机的配置模式,大体上可以分为两类:其一以CISCO交换机为代表的配置模式,其二以Huawei.H3C交换机为代表的配置模式.其实这两种配置模式并没有本质的不同,只是配置的命令名称和配置方式存在差别.如果把握了交换机的配置的基本原理,就不难掌握,且举一反三可以推及其他品牌的交换机的配置.总结来说,如果以在交换机上配置VLAN为例,可以参考以下基本原理或步骤: 交换机最初通过交换机上管理口进行连接.管理口有各种形式,在交换机上是COM口(另一端也是COM口),在交换机上是RJ45接口(另一端

ubuntu的两种网络连接模式

ubuntu的网络连接分成两种类型,一种是modern 模式, 这种模式的配置通过ifconfig命令来进行配置,重启之后失效,这种模式就是在destop 下右上角的网络连接.如图所示 modern模式的配置在/etc/network/interfaces 配置文件中不会产生对应的项,在interfaces文件只能看到lo的配置. 在/etc/network/interfaces 中配置的网络连接叫做legacy 模式,配置形式如下 注意modern 模式与legacy 模式是相互冲突的.在系统

JAVA IO ( 两种打印流 )

<!--字符打印流--> public class CharDemo { public static void main(String[] args) throws Exception { // 创建字符打印流 PrintWriter p = new PrintWriter(new FileWriter(new File("src/打印流_输出流/text.txt"), true)); p.print("黄伟强"); p.println(); p.pri

js两种生成对象模式(公有成员和成员私有)

假设有个需求,创建一个book类,有isbn码,书名,作者 :可以对isbn进行数据完整性校验:--js设计模式 <script type="text/javascript"> /** 方案一,属性全部属于公有类型,用this创建 该方式可以在构造器中对数据的完整性进行检验的控制,但对其他人员会把什么值赋给该属性而不能进行数据完整性检验的控制: **/ var Book = function(isbn,name,author){ if(!this.checkIsbn(isb