常用限流算法的应用场景和实现原理

2023-10-31

在高并发业务场景下,保护系统时,常用的"三板斧"有:"熔断、降级和限流"。今天和大家谈谈常用的限流算法的几种实现方式,这里所说的限流并非是网关层面的限流,而是业务代码中的逻辑限流。

限流算法常用的几种实现方式有如下四种:

  • 计数器

  • 滑动窗口

  • 漏桶

  • 令牌桶

下面会展开说每种算法的实现原理和他们自身的缺陷,方便以后我们在实际应用中能够根据不同的情况选择正确的限流算法。

计数器

算法思想

计数器是一种比较简单粗暴的限流算法,其思想是在固定时间窗口内对请求进行计数,与阀值进行比较判断是否需要限流,一旦到了时间临界点,将计数器清零。

固定时间窗口

面临的问题

计数器算法存在“时间临界点”缺陷。比如每一分钟限制100个请求,可以在00:00:00-00:00:58秒里面都没有请求,在00:00:59瞬间发送100个请求,这个对于计数器算法来是允许的,然后在00:01:00再次发送100个请求,意味着在短短1s内发送了200个请求,如果量更大呢,系统可能会承受不住瞬间流量,导致系统崩溃。(如下图所示)

固定时间窗口

所以计数器算法实现限流的问题是没有办法应对突发流量,不过它的算法实现起来确实最简单的,下面给出一个用Go代码实现的计数器。

代码实现

type LimitRate struct {
   rate  int           //阀值
   begin time.Time     //计数开始时间
   cycle time.Duration //计数周期
   count int           //收到的请求数
   lock  sync.Mutex    //锁
}

func (limit *LimitRate) Allow() bool {
   limit.lock.Lock()
   defer limit.lock.Unlock()

   // 判断收到请求数是否达到阀值
   if limit.count == limit.rate-1 {
      now := time.Now()
      // 达到阀值后,判断是否是请求周期内
      if now.Sub(limit.begin) >= limit.cycle {
         limit.Reset(now)
         return true
      }
      return false
   } else {
      limit.count++
      return true
   }
}

func (limit *LimitRate) Set(rate int, cycle time.Duration) {
   limit.rate = rate
   limit.begin = time.Now()
   limit.cycle = cycle
   limit.count = 0
}

func (limit *LimitRate) Reset(begin time.Time) {
   limit.begin = begin
   limit.count = 0
}

滑动窗口

算法思想

滑动窗口算法将一个大的时间窗口分成多个小窗口,每次大窗口向后滑动一个小窗口,并保证大的窗口内流量不会超出最大值,这种实现比固定窗口的流量曲线更加平滑。

普通时间窗口有一个问题,比如窗口期内请求的上限是100,假设有100个请求集中在前1s的后100ms,100个请求集中在后1s的前100ms,其实在这200ms内就已经请求超限了,但是由于时间窗每经过1s就会重置计数,就无法识别到这种请求超限。

对于滑动时间窗口,我们可以把1ms的时间窗口划分成10个小窗口,或者想象窗口有10个时间插槽time slot, 每个time slot统计某个100ms的请求数量。每经过100ms,有一个新的time slot加入窗口,早于当前时间1s的time slot出窗口。窗口内最多维护10个time slot。

滑动窗口

面临的问题

滑动窗口算法是固定窗口的一种改进,但从根本上并没有真正解决固定窗口算法的临界突发流量问题

代码实现

主要就是实现滑动窗口算法,不过滑动窗口算法一般是找出数组中连续k个元素的最大值,这里是已知最大值n (就是请求上限)如果超过最大值就不予通过。

可以参考Bilibili开源的kratos框架里circuit breaker用循环列表保存timeSlot对象的实现,他们这个实现的好处是不用频繁的创建和销毁timeslot对象。下面给出一个简单的基本实现:

type timeSlot struct {
 timestamp time.Time // 这个timeSlot的时间起点
 count     int       // 落在这个timeSlot内的请求数
}

// 统计整个时间窗口中已经发生的请求次数
func countReq(win []*timeSlot) int {
 var count int
 for _, ts := range win {
  count += ts.count
 }
 return count
}

type SlidingWindowLimiter struct {
 mu           sync.Mutex    // 互斥锁保护其他字段
 SlotDuration time.Duration // time slot的长度
 WinDuration  time.Duration // sliding window的长度
 numSlots     int           // window内最多有多少个slot
 windows      []*timeSlot
 maxReq       int // 大窗口时间内允许的最大请求数
}

func NewSliding(slotDuration time.Duration, winDuration time.Duration, maxReq int) *SlidingWindowLimiter {
 return &SlidingWindowLimiter{
  SlotDuration: slotDuration,
  WinDuration:  winDuration,
  numSlots:     int(winDuration / slotDuration),
  maxReq:       maxReq,
 }
}


func (l *SlidingWindowLimiter) validate() bool {
 l.mu.Lock()
 defer l.mu.Unlock()


 now := time.Now()
 // 已经过期的time slot移出时间窗
 timeoutOffset := -1
 for i, ts := range l.windows {
  if ts.timestamp.Add(l.WinDuration).After(now) {
   break
  }
  timeoutOffset = i
 }
 if timeoutOffset > -1 {
  l.windows = l.windows[timeoutOffset+1:]
 }

 // 判断请求是否超限
 var result bool
 if countReq(l.windows) < l.maxReq {
  result = true
 }

 // 记录这次的请求数
 var lastSlot *timeSlot
 if len(l.windows) > 0 {
  lastSlot = l.windows[len(l.windows)-1]
  if lastSlot.timestamp.Add(l.SlotDuration).Before(now) {
   // 如果当前时间已经超过这个时间插槽的跨度,那么新建一个时间插槽
   lastSlot = &timeSlot{timestamp: now, count: 1}
   l.windows = append(l.windows, lastSlot)
  } else {
   lastSlot.count++
  }
 } else {
  lastSlot = &timeSlot{timestamp: now, count: 1}
  l.windows = append(l.windows, lastSlot)
 }


 return result
}

滑动窗口实现起来代码有点多,完整可运行的测试代码可以访问我的GitHub链接 https://github.com/kevinyan815/gocookbook/issues/26 拿到,我把这个链接也放到了阅读原文里,点击即可访问

漏桶

算法思想

漏桶算法是首先想象有一个木桶,桶的容量是固定的。当有请求到来时先放到木桶中,处理请求的worker以固定的速度从木桶中取出请求进行相应。如果木桶已经满了,直接返回请求频率超限的错误码或者页面。

漏桶

适用场景

漏桶算法是流量最均匀的限流实现方式,一般用于流量“整形”。例如保护数据库的限流,先把对数据库的访问加入到木桶中,worker再以db能够承受的qps从木桶中取出请求,去访问数据库。

存在的问题

木桶流入请求的速率是不固定的,但是流出的速率是恒定的。这样的话能保护系统资源不被打满,但是面对突发流量时会有大量请求失败,不适合电商抢购和微博出现热点事件等场景的限流。

代码实现

// 一个固定大小的桶,请求按照固定的速率流出
// 请求数大于桶的容量,则抛弃多余请求

type LeakyBucket struct {
   rate       float64    // 每秒固定流出速率
   capacity   float64    // 桶的容量
   water      float64    // 当前桶中请求量
   lastLeakMs int64      // 桶上次漏水微秒数
   lock       sync.Mutex // 锁
}

func (leaky *LeakyBucket) Allow() bool {
   leaky.lock.Lock()
   defer leaky.lock.Unlock()

   now := time.Now().UnixNano() / 1e6
   // 计算剩余水量,两次执行时间中需要漏掉的水
   leakyWater := leaky.water - (float64(now-leaky.lastLeakMs) * leaky.rate / 1000)
   leaky.water = math.Max(0, leakyWater)
   leaky.lastLeakMs = now
   if leaky.water+1 <= leaky.capacity {
      leaky.water++
      return true
   } else {
      return false
   }
}

func (leaky *LeakyBucket) Set(rate, capacity float64) {
   leaky.rate = rate
   leaky.capacity = capacity
   leaky.water = 0
   leaky.lastLeakMs = time.Now().UnixNano() / 1e6
}

令牌桶

算法思想

令牌桶是反向的"漏桶",它是以恒定的速度往木桶里加入令牌,木桶满了则不再加入令牌。服务收到请求时尝试从木桶中取出一个令牌,如果能够得到令牌则继续执行后续的业务逻辑。如果没有得到令牌,直接返回访问频率超限的错误码或页面等,不继续执行后续的业务逻辑。

特点:由于木桶内只要有令牌,请求就可以被处理,所以令牌桶算法可以支持突发流量。

令牌桶

同时由于往木桶添加令牌的速度是恒定的,且木桶的容量有上限,所以单位时间内处理的请求书也能够得到控制,起到限流的目的。假设加入令牌的速度为 1token/10ms,桶的容量为500,在请求比较的少的时候(小于每10毫秒1个请求)时,木桶可以先"攒"一些令牌(最多500个)。当有突发流量时,一下把木桶内的令牌取空,也就是有500个在并发执行的业务逻辑,之后要等每10ms补充一个新的令牌才能接收一个新的请求。

参数设置

木桶的容量  - 考虑业务逻辑的资源消耗和机器能承载并发处理多少业务逻辑。

生成令牌的速度 - 太慢的话起不到“攒”令牌应对突发流量的效果。

适用场景

适合电商抢购或者微博出现热点事件这种场景,因为在限流的同时可以应对一定的突发流量。如果采用漏桶那样的均匀速度处理请求的算法,在发生热点时间的时候,会造成大量的用户无法访问,对用户体验的损害比较大。

代码实现

type TokenBucket struct {
   rate         int64 //固定的token放入速率, r/s
   capacity     int64 //桶的容量
   tokens       int64 //桶中当前token数量
   lastTokenSec int64 //上次向桶中放令牌的时间的时间戳,单位为秒

   lock sync.Mutex
}

func (bucket *TokenBucket) Take() bool {
   bucket.lock.Lock()
   defer bucket.lock.Unlock()

   now := time.Now().Unix()
   bucket.tokens = bucket.tokens + (now-bucket.lastTokenSec)*bucket.rate // 先添加令牌
   if bucket.tokens > bucket.capacity {
      bucket.tokens = bucket.capacity
   }
   bucket.lastTokenSec = now
   if bucket.tokens > 0 {
      // 还有令牌,领取令牌
      bucket.tokens--
      return true
   } else {
      // 没有令牌,则拒绝
      return false
   }
}

func (bucket *TokenBucket) Init(rate, cap int64) {
   bucket.rate = rate
   bucket.capacity = cap
   bucket.tokens = 0
   bucket.lastTokenSec = time.Now().Unix()
}

总结

这几种常用的限流算法实现方式,相互之间没有所谓的"谁是所谓的更优秀的实现方法",主要还是看具体用在哪里。这几个限流算法的简单实现和测试代码我都放到GitHub上了,除此之外仓库里还有不少有用的小程序,有兴趣的可以点击阅读原文自取。


看到这里了,如果喜欢我的文章就帮我点个赞吧,我会每周通过技术文章分享我的所学所见和第一手实践经验,感谢你的支持。微信搜索关注公众号「网管叨bi叨」每周教会你一个进阶知识,还有专门写给开发工程师的Kubernetes入门教程。

- END -

关注公众号,每周分享给你一个进阶知识

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

常用限流算法的应用场景和实现原理 的相关文章

  • MongoTemplate upsert - 从 pojo 进行更新的简单方法(哪个用户已编辑)?

    这是一个简单的 pojo public class Description private String code private String name private String norwegian private String en
  • jvm 次要版本与编译器次要版本

    当运行使用具有相同主要版本但次要版本高于 JVM 的 JDK 编译的类时 JVM 会抛出异常吗 JDK 版本并不重要 类文件格式版本 http blogs oracle com darcy entry source target class
  • Google App Engine with Java - 运行 javac.exe 编译器时出错

    在 Windows XP 上 刚刚下载并解压谷歌应用程序引擎java sdk to C Program Files appengine java sdk 我已经安装了jdk C Program Files Java jdk1 6 0 20
  • 如何开始使用 Chainsaw for Log4j?

    我想开始使用 Chainsaw v2 几乎没有关于它的信息 我只找到了this http www velocityreviews com forums t140105 help using chainsaw for log4j html 但
  • 如何比较 Struts 2 中 url 请求参数中的单个字符

    我正在读取具有单个字符的 url 参数 它将是Y or N 我必须写一个条件来检查它是否Y or N并做相应的事情 这是我写的 但似乎不起作用 总是转到其他地方 网址是
  • 哪个 Swing 布局管理器可以获得我想要的布局?

    我正在尝试按照这个模型制作一个基本的登录菜单 我决定将整个菜单放入 JPanel 中 以便在连接成功后我可以切换到另一个面板 所以我决定使用 Borderlayout 将标题放在北区 将连接按钮放在南区 我将边框布局的中心本身设置为面板 我
  • RMI 中的引用传递问题? [复制]

    这个问题在这里已经有答案了 有人可以告诉我我错在哪里 为什么这个 RMI 聊天应用程序不起作用 目标是通过远程对象或序列化对象实现客户端 服务器和逻辑之间的解耦 import javax swing import java awt even
  • 请参阅 Java EE eclipse 调试中的 POST 参数

    我在调试 Java EE 方面没有经验 我更像是一个 javascript 人 我需要查看哪些 HTTP POST 参数到达服务器端 我在表单将其操作指向的 jsp 文件中放置了一个断点 现在我在调试变量窗口中找不到 POST 内容 他们在
  • java.lang.LinkageError:尝试重复的类定义

    为什么会发生错误以及如何修复它 02 13 02 pool 4 thread 2 WARN Exception in thread pool 4 thread 2 02 13 02 pool 4 thread 2 WARN java lan
  • 是否有任何API可以将Microsoft Exchange服务器与Java应用程序集成以进行任务同步?

    我正在尝试将 Java Web 应用程序与 Microsoft Exchange 服务器集成以实现双向日历 即任务 同步 是否有用于此集成的 Java 开源 商业 API 谢谢 文卡特 看一眼j 交易所 http sourceforge n
  • 如何拦截 REST 端点以接收所有标头?

    我当前的代码是 Path login RequestScoped public class LoginResource GET SecurityChecked public Response getUser HeaderParam AUTH
  • 定期更新 SWT 会导致 GUI 冻结

    Problem 当 GUI 字段定期更新时 SWT 会冻结 我想要一个基于 SWT 的 GUI 其中文本字段的值会定期递增 最初我从单独的线程访问 textField 导致抛出异常 线程 Thread 0 org eclipse swt S
  • 从 @JsonProperty 值获取枚举常量

    我有一个标有 JsonProperty 的枚举 用于使用 Jackson 进行 JSON 序列化 反序列化 并且希望获取给定字符串 JsonProperty 的枚举值 public enum TimeBucket JsonProperty
  • 异步迭代器

    我有以下代码 while slowIterator hasNext performLengthTask slowIterator next 由于迭代器和任务都很慢 因此将它们放入单独的线程中是有意义的 这是对迭代器包装器的快速而肮脏的尝试
  • 我想在java中使用XQuery进行Xml处理

    我想用XQuery用于从 java 中的 Xml 获取数据 但我没有得到需要为此添加哪个 Jar 我在谷歌上搜索了很多 但没有得到任何有用的例子 例如我得到以下链接 https docs oracle com database 121 AD
  • Golang 正则表达式在字符串之间替换

    我有一些可能采用以下形式的字符串 MYSTRING MYSTRING n MYSTRING n MYSTRING randomstringwithvariablelength n 我希望能够将其正则表达式为MYSTRING foo 基本上替
  • 警告:无法更改每个人的权限:

    当运行 Java 快速入门示例时https developers google com drive web quickstart java hl hu https developers google com drive web quicks
  • 公共方法与公共 API

    在干净的代码书中 有一个观点是 公共 API 中的 Javadocs 同样 Effective java 一书也有这样的内容 项目 56 为所有公开的 API 元素编写文档注释 所以这就是我的问题 所有公共方法都被视为公共 API 吗 它们
  • 为什么这个私人浮动字段变为零?

    我有一些奇怪的行为 我很难向自己解释 称为 textureScale 的浮点字段变为零 如果某些代码正在更改该值 则可以解释这一点 然而 我希望能够通过将其设置为 私有最终浮点 来导致构建失败 或者至少是运行时异常 那么无论更改该值都将失败
  • 直接从一个通道发送到另一个通道

    当从一个通道直接发送到另一个通道时 我偶然发现了令人惊讶的行为 package main import fmt func main my chan make chan string chan of chans make chan chan

随机推荐

  • Linux(centos7.9)常用命令大全及基础知识

    linux中数组的索引从0开始 其他默认从1开始 例如没有第0列 从第1列开始 在Unix中一切 包括网络套接口 都是文件 在命令行中 无论几个空格 都当成一个空格看待 在linux中 在命令行中通过执行命令的方式来改变系统或软件配置时 效
  • mysql数据库管理工具 h_几款桌面MYSQL管理工具

    1 Navicat Navicat是一个强大的MySQL数据库管理和开发工具 Navicat导航为专业开发者提供了一套强大的足够尖端的工具 但它对于新用户仍然 是易于 学习 Navicat 使用了极好的图形用户界面 GUI 可以让你用一种安
  • 数据预处理——数据无量纲化(归一化、标准化)

    文章目录 1 数据归一化 1 1 数据归一化定义 1 2 MinMaxScaler 归一化 1 3 MinMaxScaler 使用样例 2 数据标准化 2 1 数据标准化定义 2 2 StandardScaler 标准化 2 3 Stand
  • python如何求列表的平均值_python如何求列表平均值?

    推荐教程 python视频教程 python如何求列表平均值 python函数求列表平均值的方法 用法 mean matrix axis 0 其中matrix为一个矩阵 axis为参数 以m n矩阵举例 axis不设置值 对 m n 个数求
  • mysql基础命令

    1 常用命令 1 create database db name 创建数据库 2 show databases 显示所有的数据库 3 drop database db name 删除数据库 4 show tables 显示数据表 5 des
  • 三菱j4伺服中文说明书_三菱伺服抱闸伺服的使用方法

    刹车原理 伺服电机的刹车抱闸和普通的电磁抱闸原理是一样的 靠电磁线圈产生磁场吸力 克服机械刹车片的弹簧制动力矩 驱动机械刹车片的分开 释放电机轴 一般三菱伺服的刹车都是直流24V电源 他的刹车是不分正负极 很多初次使用的工控人员都会纠结这个
  • 网站设计之常见简单实用的JavaScript特效总结(上篇)

    这篇主要是总结JavaScript常见简单实用的特效 主要从代码量短 简单实用几个方面进行叙述 其中特效包括 1 鼠标悬停图片切换查看器 2 鼠标移动图片放大 3 鼠标移动切换内容 4 贵财下拉菜单案例 5 JS图片放大镜功能 类似淘宝 6
  • 【无标题】文件查找、运行项目(网站)HTML5(H5)、压缩---解压缩

    一 登录系统 用户名 root 密钥对 安全组 云服务器 来源 0 0 0 0 0 端口 ALL TCP 80 策略 允许 物理服务器 虚拟机 systemctl stop firewalld 关闭防火墙 setenforce 0 关闭se
  • ethers.js 应用(助记词、地址、私钥)

    1 创建助记词 const ethers require ethers let wallet ethers Wallet createRandom let mnemonic wallet mnemonic console log mnemo
  • MybatisPlus整合Flowable出现的坑

    MybatisPlus整合Flowable出现的坑 摘要 现在在项目中使用的MybatisPlus 最近研究了一下流程框架Flowable 看了很多技术文档博客 打算直接整合进去 先记录一下遇到的问题 问题 Description file
  • #define宏定义详解

    define宏定义 1 常规用法 无参宏 define PI 3 1415926 define EN 1e5 定义指数1 10e5 cout lt
  • SpringBoot学习笔记之日志处理

    spring boot内部使用Commons Logging来记录日志 但也保留外部接口可以让一些日志框架来进行实现 例如Java Util Logging Log4J2还有Logback 如果你想用某一种日志框架来进行实现的话 就必须先配
  • vue中使用loading

    因为有很多组件需要loading 所以我们把loading写为组件 在全局中都可以使用 而选择的loading 最好是css3动画写的 如果用图片 图片本身就是需要请求的 在网上找了一个css3动画 如下 loading中的代码
  • osgEarth的Rex引擎原理分析(一一四)rex与mp引擎的关系

    目标 一一三 中的问题201 rex与mp都是osgEarth加载地理高程和影像的引擎 rex比mp新 功能更强大 rex引擎支持随机瓦片加载 地图颜色渐变 更快的添加删除 待继续分析列表 9 earth文件中都有哪些options 九 中
  • wangeditor富文本引用、表格使用问题

    wangeditor富文本组件问题 问题介绍 具体情况 解决方案 css修改 说明 问题介绍 本文记录了wangeditor开发中遇到的一个问题 之前在使用wangeditor的时候因为时间紧张没有过多研究 后续项目测试 测出来发现编辑器中
  • SAP系统权限配置一

    1 系统权限的重要性 2 SAP系统环境 在测试环境做客户化权限的配置测试 开发系统中做对应的开发 不允许直接正式系统环境中做客户化配置以及开发 只能通过传输的形式 3 SAP权限的基本概念 这边后续都是ABAP开发顾问来分配和定义的 4
  • 在项目中,关于前端实现数据可视化的技术选择

    前言 在项目中 数据可视化以图表 报表类型为主 需求背景 技术框架是Vue2 x版本 组件库是Ant Design of Vue 能够支撑足够多的图表类型开发 图表大小 位置能够随意变动 图表样式需要支持丰富多样的用户配置 强大 开放的图表
  • 计算机专业数学建模结课论文,数学建模论文范文2篇

    利用数学知识解决现实生活的具体问题了成为当今数学界普遍关注的内容 利用建立数学模型解决实际问题的数学建模活动也应运而生了 下面是秋天网小编为大家整理的数学建模论文 供大家参考 数学建模论文范文一 初中数学建模教学研究 数学 源于人们对生产与
  • Snapd出错记录

    突然断电导致无法访问所有应用商店安装的应用 即snapd出问题 访问systemctl status snapd service无法访问 如图 查阅了很多资料 有用的只有重新安装 重新安装snapd sudo apt autoremove
  • 常用限流算法的应用场景和实现原理

    在高并发业务场景下 保护系统时 常用的 三板斧 有 熔断 降级和限流 今天和大家谈谈常用的限流算法的几种实现方式 这里所说的限流并非是网关层面的限流 而是业务代码中的逻辑限流 限流算法常用的几种实现方式有如下四种 计数器 滑动窗口 漏桶 令