用go实现限流算法

文章目录

  • 固定窗口
      • 优缺点:
      • 适用场景:
      • 总结:
  • 滑动窗口
      • 优缺点:
      • 适用场景:
      • 总结:
  • 漏桶限流器
      • 优缺点:
      • 适用场景:
      • 总结:
  • 令牌桶
      • 优缺点:
      • 适用场景:
      • 总结:
  • 总结

今天总结一下四种限流算法,通过图形和代码的方式去一点点深入的探讨这些算法的一些本质,并且讲解以下优缺点。方便在以后的学习或者工作中,可以很好的去运用这些知识点。然后挑选最利于场景的一些算法。

固定窗口

在这里插入图片描述

优缺点:

优点

  • 简单易实现:固定窗口算法的逻辑简单,容易理解和实现。
  • 公平性:每个请求都有机会在窗口开始时被处理,避免了某些请求长时间等待的问题。

缺点

  • 临界点问题:在窗口切换的瞬间,可能会有大量请求同时到达,导致短时间内的请求量超过限制,这可能会对系统造成压力。
  • 不够灵活:窗口大小固定,无法根据实际流量动态调整。

适用场景:

  • 适用于请求频率相对稳定的场景:如果系统能够承受短时间内的高并发请求,固定窗口算法是一个不错的选择。
  • 适用于需要简单限流策略的场景:对于那些不需要复杂限流逻辑的系统,固定窗口算法提供了一个简单而有效的解决方案。

总结:

固定窗口限流算法是一种基础的限流策略,适用于需要简单限流控制的场景。然而,它可能不适合那些需要更平滑流量控制或需要根据实际流量动态调整限流策略的系统。在实际应用中,可能需要结合其他限流算法(如滑动窗口或令牌桶算法)来满足更复杂的业务需求。

package limiter

import (
    "sync"
    "time"
)

// FixedWindowLimiter 结构代表一个固定窗口限流器。
// 它使用固定大小的时间窗口来限制请求的数量。
type FixedWindowLimiter struct {
    limit    int           // 请求上限,窗口内允许的最大请求数
    window   time.Duration // 窗口时间大小,即时间窗口的长度
    counter  int           // 计数器,记录当前窗口内的请求数
    lastTime time.Time     // 上一次请求的时间
    mutex    sync.Mutex    // 互斥锁,用于同步,避免并发访问导致的问题
}

// NewFixedWindowLimiter 构造函数创建并初始化一个新的 FixedWindowLimiter 实例。
func NewFixedWindowLimiter(limit int, window time.Duration) *FixedWindowLimiter {
    return &FixedWindowLimiter{
       limit:    limit,
       window:   window,
       lastTime: time.Now(), // 初始化时设置当前时间为窗口开始时间
    }
}

// TryAcquire 尝试获取一个请求的机会。
// 如果当前窗口内请求数未达到上限,增加计数器并返回 true。
// 如果请求数已达到上限或窗口已过期,返回 false。
func (l *FixedWindowLimiter) TryAcquire() bool {
    l.mutex.Lock()
    defer l.mutex.Unlock()

    now := time.Now()
    // 检查当前时间与上次请求时间差是否超过窗口大小
    if now.Sub(l.lastTime) > l.window {
       l.counter = 0    // 如果窗口过期,重置计数器
       l.lastTime = now // 更新窗口开始时间为当前时间
    }
    // 如果当前请求数未达到上限,允许请求
    if l.counter < l.limit {
       l.counter++ // 请求成功,增加计数器
       return true // 返回 true 表示请求已成功获取
    }
    // 如果请求数已达到上限,请求失败
    return false
}

滑动窗口

[图片]

优缺点:

优点

  • 平滑处理请求:通过将大窗口划分为多个小窗口,可以更平滑地处理请求,避免固定窗口算法的临界点问题。
  • 灵活性:可以根据需要调整小窗口的大小和数量,以适应不同的流量模式。
  • 并发性能:使用读写锁允许多个并发读取操作,提高了并发性能。

缺点

  • 复杂性:相较于固定窗口算法,滑动窗口算法的实现和维护更复杂。
  • 内存****使用:需要存储每个小窗口的计数器,可能会占用更多的内存,尤其是在窗口数量很大时。

适用场景:

  • 适用于请求流量波动较大:如果系统需要处理不均匀的请求流量,滑动窗口算法可以更好地平滑流量峰值。
  • 适用于需要更精细控制:当需要根据实际流量动态调整限流策略时,滑动窗口算法提供了更高的灵活性。
  • 不适用于简单场景:对于简单的限流需求,固定窗口算法可能更简单、更易于实现。

总结:

滑动窗口限流算法是一种有效的流量控制机制,特别适合处理请求流量波动较大的场景。然而,它需要更多的计算和内存资源,因此在资源受限的环境中可能不是最佳选择。在实际应用中,需要根据具体的业务需求和系统资源来选择合适的限流算法。

package limiter

import (
    "errors"
    "sync"
    "time"
)

// SlidingWindowLimiter 滑动窗口限流器,用于控制请求的速率。
type SlidingWindowLimiter struct {
    limit        int           // 窗口内允许的最大请求数
    window       int64         // 窗口时间大小(纳秒)
    smallWindow  int64         // 小窗口时间大小(纳秒)
    smallWindows int64         // 窗口内小窗口的数量
    counters     map[int64]int // 每个小窗口的请求计数
    mutex        sync.RWMutex  // 使用读写锁提高并发性能
}

// NewSlidingWindowLimiter 创建并初始化滑动窗口限流器。
func NewSlidingWindowLimiter(limit int, window, smallWindow time.Duration) (*SlidingWindowLimiter, error) {
    if int64(window%smallWindow) != 0 {
       return nil, errors.New("window size must be divisible by the small window size")
    }
    return &SlidingWindowLimiter{
       limit:        limit,
       window:       int64(window),
       smallWindow:  int64(smallWindow),
       smallWindows: int64(window / smallWindow),
       counters:     make(map[int64]int),
    }, nil
}

// TryAcquire 尝试在当前窗口内获取一个请求的机会。
func (l *SlidingWindowLimiter) TryAcquire() bool {
    l.mutex.RLock() // 读锁,允许多个并发读取
    defer l.mutex.RUnlock()

    now := time.Now().UnixNano()
    currentSmallWindow := now / l.smallWindow * l.smallWindow // 当前小窗口的起始点

    // 清理过期的小窗口计数器
    l.cleanExpiredWindows(now)

    // 检查并更新当前小窗口的计数
    l.mutex.Lock() // 写锁,更新计数器
    defer l.mutex.Unlock()

    count, exists := l.counters[currentSmallWindow]
    if !exists || count < l.limit {
       l.counters[currentSmallWindow] = count + 1
       return true
    }
    return false
}

// cleanExpiredWindows 清理已过期的小窗口计数器。
func (l *SlidingWindowLimiter) cleanExpiredWindows(now int64) {
    startSmallWindow := now/l.smallWindow*l.smallWindow - l.window
    for smallWindow := range l.counters {
       if smallWindow < startSmallWindow {
          delete(l.counters, smallWindow)
       }
    }
}

// 注意:cleanExpiredWindows 方法应该在持有读锁的情况下调用,以避免在遍历和修改计数器时产生竞态条件。

漏桶限流器

[图片]

[图片]

优缺点:

优点

  • 平滑处理请求:漏桶算法能够平滑处理请求,即使在流量突发的情况下也能保持稳定的处理速率。
  • 简单易实现:相对于其他复杂的限流算法,漏桶算法的实现较为简单。
  • 并发性能:使用读写锁允许多个并发读取操作,提高了并发性能。

缺点

  • 固定速率:漏桶算法以固定速率处理请求,可能不适合需要动态调整处理速率的场景。
  • 可能的延迟:在高流量情况下,请求可能需要等待桶内水位下降才能被处理,这可能导致延迟。

适用场景:

  • 适用于请求处理速率相对稳定的场景:例如,定时任务执行、消息队列处理等。
  • 适用于需要平滑流量的场景:例如,防止短时间内大量请求对系统造成压力。
  • 不适用于需要快速响应的场景:例如,实时性要求高的在线游戏或交易系统。

总结:

漏桶限流算法是一种有效的流量控制机制,特别适合处理请求速率相对稳定的服务。然而,它可能不适合那些需要快速响应或动态调整处理速率的系统。在实际应用中,需要根据具体的业务需求和系统资源来选择合适的限流算法。

package limiter

import (
    "errors"
    "math"
    "sync"
    "time"
)

// LeakyBucketLimiter 漏桶限流器
type LeakyBucketLimiter struct {
    peakLevel       int          // 最高水位
    currentLevel    int          // 当前水位
    currentVelocity int          // 水流速度/秒
    lastTime        time.Time    // 上次放水时间
    mutex           sync.RWMutex // 使用读写锁提高并发性能
}

// NewLeakyBucketLimiter 初始化漏桶限流器
func NewLeakyBucketLimiter(peakLevel, currentVelocity int) (*LeakyBucketLimiter, error) {
    if currentVelocity <= 0 {
       return nil, errors.New("currentVelocity must be greater than 0")
    }
    if peakLevel < currentVelocity {
       return nil, errors.New("peakLevel must be greater than or equal to currentVelocity")
    }
    return &LeakyBucketLimiter{
       peakLevel:       peakLevel,
       currentLevel:    0, // 初始化时水位为0
       currentVelocity: currentVelocity,
       lastTime:        time.Now(),
    }, nil
}

// TryAcquire 尝试获取处理请求的权限
func (l *LeakyBucketLimiter) TryAcquire() bool {
    l.mutex.RLock() // 读锁,允许多个并发读取
    defer l.mutex.RUnlock()

    // 如果上次放水时间距今不到1秒,不需要放水
    now := time.Now()
    interval := now.Sub(l.lastTime)

    l.mutex.Lock() // 写锁,更新水位
    defer l.mutex.Unlock()

    // 计算放水后的水位
    if interval >= time.Second {
       l.currentLevel = int(math.Max(0, float64(l.currentLevel)-(interval/time.Second).Seconds()*float64(l.currentVelocity)))
       l.lastTime = now
    }
    // 尝试增加水位
    if l.currentLevel < l.peakLevel {
       l.currentLevel++
       return true
    }
    return false
}

令牌桶

[图片]

[图片]

优缺点:

优点

  • 允许突发流量:令牌桶算法允许在桶中积累令牌,从而允许一定程度的突发流量。
  • 平滑流量:通过控制令牌的生成速率,可以平滑处理请求。
  • 简单易实现:算法实现相对简单,易于理解和维护。

缺点

  • 可能的延迟:在桶中没有足够令牌的情况下,请求需要等待令牌生成,可能导致延迟。
  • 固定速率:虽然允许突发流量,但令牌的生成速率是固定的,不够灵活。

适用场景:

  • 适用于需要平滑流量的场景:例如,网络流量控制、API 速率限制等。
  • 适用于允许一定程度突发流量的场景:例如,促销活动期间的流量突增。
  • 不适用于需要严格实时处理的场景:例如,实时交易系统,因为请求可能需要等待令牌生成。

总结:

令牌桶限流算法是一种有效的流量控制机制,特别适合处理需要平滑流量和允许一定程度突发流量的场景。然而,它可能不适合那些需要快速响应或动态调整处理速率的系统。在实际应用中,需要根据具体的业务需求和系统资源来选择合适的限流算法。

package limiter

import (
    "sync"
    "time"
)

// TokenBucketLimiter 令牌桶限流器
type TokenBucketLimiter struct {
    capacity      int        // 容量
    currentTokens int        // 令牌数量
    rate          int        // 发放令牌速率/秒
    lastTime      time.Time  // 上次发放令牌时间
    mutex         sync.Mutex // 避免并发问题
}

// NewTokenBucketLimiter 创建一个新的令牌桶限流器实例。
func NewTokenBucketLimiter(capacity, rate int) *TokenBucketLimiter {
    return &TokenBucketLimiter{
       capacity:      capacity,
       rate:          rate,
       lastTime:      time.Now(),
       currentTokens: 0, // 初始化时桶中没有令牌
    }
}

// TryAcquire 尝试从令牌桶中获取一个令牌。
func (l *TokenBucketLimiter) TryAcquire() bool {
    l.mutex.Lock()
    defer l.mutex.Unlock()

    now := time.Now()
    interval := now.Sub(l.lastTime) // 计算时间间隔

    // 如果距离上次发放令牌超过1秒,则发放新的令牌
    if interval >= time.Second {
       // 计算应该发放的令牌数量,但不超过桶的容量
       newTokens := int(interval/time.Second) * l.rate
       l.currentTokens = minInt(l.capacity, l.currentTokens+newTokens)

       // 更新上次发放令牌的时间
       l.lastTime = now
    }

    // 如果桶中没有令牌,则请求失败
    if l.currentTokens == 0 {
       return false
    }

    // 桶中有令牌,消费一个令牌
    l.currentTokens--

    return true
}

// minInt 返回两个整数中的较小值。
func minInt(a, b int) int {
    if a < b {
       return a
    }
    return b
}package limiter

import (
    "sync"
    "time"
)

// TokenBucketLimiter 令牌桶限流器
type TokenBucketLimiter struct {
    capacity      int        // 容量
    currentTokens int        // 令牌数量
    rate          int        // 发放令牌速率/秒
    lastTime      time.Time  // 上次发放令牌时间
    mutex         sync.Mutex // 避免并发问题
}

// NewTokenBucketLimiter 创建一个新的令牌桶限流器实例。
func NewTokenBucketLimiter(capacity, rate int) *TokenBucketLimiter {
    return &TokenBucketLimiter{
       capacity:      capacity,
       rate:          rate,
       lastTime:      time.Now(),
       currentTokens: 0, // 初始化时桶中没有令牌
    }
}

// TryAcquire 尝试从令牌桶中获取一个令牌。
func (l *TokenBucketLimiter) TryAcquire() bool {
    l.mutex.Lock()
    defer l.mutex.Unlock()

    now := time.Now()
    interval := now.Sub(l.lastTime) // 计算时间间隔

    // 如果距离上次发放令牌超过1秒,则发放新的令牌
    if interval >= time.Second {
       // 计算应该发放的令牌数量,但不超过桶的容量
       newTokens := int(interval/time.Second) * l.rate
       l.currentTokens = minInt(l.capacity, l.currentTokens+newTokens)

       // 更新上次发放令牌的时间
       l.lastTime = now
    }

    // 如果桶中没有令牌,则请求失败
    if l.currentTokens == 0 {
       return false
    }

    // 桶中有令牌,消费一个令牌
    l.currentTokens--

    return true
}

// minInt 返回两个整数中的较小值。
func minInt(a, b int) int {
    if a < b {
       return a
    }
    return b
}

总结

上面的就是对于四种限流算法的描述,可以通过其中的代码,还有一些具体的分析,利用流程图和模拟图的整体搭配,保证了学习效率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/802419.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

保障低压设备安全!中国星坤连接器精密工艺解析!

在现代电子设备中&#xff0c;连接器扮演着至关重要的角色&#xff0c;它们是电子系统之间沟通的桥梁。随着技术的发展&#xff0c;对连接器的需求也在不断提升&#xff0c;特别是在低电压应用领域。中国星坤最新推出的低压连接器&#xff0c;以其精密性和安全性&#xff0c;为…

AI算法18-最小角回归算法Least Angle Regression | LARS

​​​ 最小角回归算法简介 最小角回归&#xff08;Least Angle Regression, LAR&#xff09;是一种用于回归分析的统计方法&#xff0c;它在某些方面类似于最小二乘回归&#xff0c;但提供了一些额外的优点。最小角回归由Bradley Efron等人提出&#xff0c;主要用于处理具有…

10.1 标注、注记图层和注记整体说明

文章目录 前言标注、注记图层和注记QGis中的标注QGis中的注释(Annotation)图层QGis中的注记 总结 前言 介绍标注、注记图层和注记说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 标注、注记图层和注记 有时地图需要使用一些文字信息说明其中的地理要素或其…

如何用一个例子向10岁小孩解释高并发实时服务的单线程事件循环架构

I/O密集型进程和CPU密集型进程 聊天应用程序、MMO&#xff08;大型多人在线&#xff09;游戏、金融交易系统、等实时服务需要处理大量并发流量和实时数据。 这些服务是I/O密集型的&#xff0c;因为它们花费大量资源处理输入输出操作&#xff0c;例如高吞吐量、低延迟网络通信…

泉盛UV-K5扩容2Mbit EEPROM

泉盛UV-K5扩容2Mbit EEPROM 步骤 分离前面板与背板。 拆下电池&#xff0c;底部有个空隙&#xff0c;从缝隙撬开背板。分离前面板时注意喇叭连接线&#xff0c;不要扯断了。 分离屏幕。 先从箭头位置向上挑起&#xff0c;屏幕稍微松动即可左右晃动&#xff0c;直至完全取出。注…

leetcode日记(40)N皇后

一开始没看到不能同斜线&#xff0c;以为只要不同行不同列就行&#xff0c;本来想先列出每一行的Q都不同位置的棋盘然后进行排列组合就行&#xff0c;后来才发现还有限制&#xff08;后来又想了一下&#xff0c;感觉可以先用这种思路然后去除有同一斜线的棋盘摆列&#xff09; …

【手写数据库内核组件】0501多线程并发模型,任务分发多工作者执行架构实现,多线程读写状态时volatile存储类型使用技巧

0501 多线程管理 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章目录 0501 多…

2024年金航标和萨科微扩张

近年电子信息产业链的外迁和世界经济的低迷&#xff0c;各行各业都很卷&#xff0c;加班加点但业绩负增长是常态&#xff0c;互联网大厂阿里巴巴大裁员、字节跳动裁到了大动脉、京东刘强东抛弃躺平的兄弟、深圳华强北做电子元器件的老板老板娘们一脸茫然&#xff0c;周围都弥漫…

使用工作日志 - 更快地恢复专注并理清思路

原文&#xff1a;Charles Fval - 2024.07.12 你正在处理计算机科学中最复杂的问题&#xff1a;修复部署管道上的权限。这已经是你开始处理这个简单任务的第 4 天了。你的经理明确告诉你&#xff0c;你在这方面的表现远低于她对一个中期实习生的期望。你的同事们都尽量远离你&a…

华为OD 机试真题 - 分割均衡字符串(Python)

题目描述 均衡串定义:字符串只包含两种字符&#xff0c;且两种字符的个数相同。 给定一个均衡字符串&#xff0c;请给出可分割成新的均衡子串的最大个数。 约定字符串中只包含大写的’X"和’Y’两种字符。 输入描述 均衡串:XXYYXY 字符串的长度[2,10000]。给定的字符…

南京邮电大学统计学课程实验2 用EXCEL进行参数估计假设检验 指导

一、实验描述 实验目的 1、学会用Excel进行参数估计&#xff1b; 2、学会用Excel进行z检验-双样本平均差检验&#xff1b; 实验环境 实验中使用以下软件和硬件设备 &#xff08;1&#xff09;Windows XP操作系统&#xff1b; &#xff08;2&#xff09;PC机、EXCEL软件&…

[Vulnhub] digitalworld.local-JOY snmp+ProFTPD权限提升

信息收集 IP AddressOpening Ports192.168.101.150TCP:21,22,25,80,110,139,143,445,465,587,993,995 $ nmap -p- 192.168.101.150 --21,22,25,min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 21/tcp open ftp ProFTPD | ftp-anon: Anonymous FTP logi…

Python 面向对象编程,创建类和对象

面向对象编程&#xff08;Object-Oriented Programming&#xff0c;简称 OOP&#xff09;是一种程序设计范式&#xff0c;旨在提高软件的可维护性、可扩展性和复用性。OOP 的核心思想是将数据和操作这些数据的代码封装在一起&#xff0c;通过类和对象来组织程序&#xff0c;使程…

Windows系统中MySQL的安装和卸载(详细包含msi和zip下载方式,以及完全卸载方法,易出现问题及解决方案等)

MySQL的安装: 第一种:msi安装(交简单,但是不能自定义安装路径) 下载地址:https://dev.mysql.com/downloads/installer/ 选择历史版本 选择安装版本,这里我选择的是8.0.37的版本,然后点击Download下载离线安装包 如下图即为下载好的版本,双击打开安装 出现如下情况,…

vue3中基于dayjs实现日历

import dayjs from dayjs export const useCreateCander () > {let calendarDay []// 当前年&#xff0c;去年&#xff0c;明年let year dayjs().year()let prvYear year - 1let nextYear year 1// 当前月、上月、下月let month dayjs().month() 1let prvMonth mon…

CentOS 7 Web面板的文件管理器说明

在使用CentOS 7 Web Panel&#xff08;CWP7&#xff09;时&#xff0c;偶尔要求在服务器曲面上修改&#xff0c;创建&#xff0c;编辑或删除文件。 最简单&#xff0c;最直接的方式是通过利用CWP7的内置文件管理器。 本文将详细介绍如何启动它&#xff0c;使用它&#xff0c;以…

c++信号和槽机制的轻量级实现,sigslot 库介绍及使用

Qt中的信号与槽机制很好用&#xff0c;然而只在Qt环境中。在现代 C 编程中&#xff0c;对象间的通信是一个核心问题。为了解决这个问题&#xff0c;许多库提供了信号和槽&#xff08;Signals and Slots&#xff09;机制。今天推荐分享一个轻量级的实现&#xff1a;sigslot 库。…

基于LSTM及其变体的回归预测

1 所用模型 代码中用到了以下模型&#xff1a; 1. LSTM&#xff08;Long Short-Term Memory&#xff09;&#xff1a;长短时记忆网络&#xff0c;是一种特殊的RNN&#xff08;循环神经网络&#xff09;&#xff0c;能够解决传统RNN在处理长序列时出现的梯度消失或爆炸的问题。L…

MBR40150FCT-ASEMI无人机专用MBR40150FCT

编辑&#xff1a;ll MBR40150FCT-ASEMI无人机专用MBR40150FCT 型号&#xff1a;MBR40150FCT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 批号&#xff1a;最新 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;40A 最大循环峰值反向电压&#xff08;VRRM&a…

typeorm实体多对多关系指定表名与关联字段

表结构 user 用户表结构 course 课程表结构 user_course 用户课程表 (每个用户可以有多个课程, 每个课程可以有多个用户, 该表用以建立多对多关系) 实体 user.entity.ts Entity(user, { schema: test }) export class User {PrimaryGeneratedColumn({ type: int, name: id }…