用go实现限流算法

news2024/9/22 23:17:06

文章目录

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

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

固定窗口

在这里插入图片描述

优缺点:

优点

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

缺点

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

适用场景:

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

总结:

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

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
}

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

定义损失函数并以此训练和评估模型

基础神经网络模型搭建 【Pytorch】数据集的加载和处理&#xff08;一&#xff09; 【Pytorch】数据集的加载和处理&#xff08;二&#xff09; 损失函数计算模型输出和目标之间的距离。通过torch.nn 包可以定义一个负对数似然损失函数&#xff0c;负对数似然损失对于训练具有多…

炎炎夏日,这份锂电AGV叉车保养指南赶紧收藏!

AGV 随着工厂自动化、计算机集成制造系统技术的逐步发展以及柔性制造系统、自动化立体仓库的广泛应用&#xff0c;AGV(Automatic GuidedVehicle)即自动导引车作为联系和调节离散型物流系统以使其作业连续化的必要的自动化搬运装卸手段&#xff0c;其应用范围和技术水平得到了迅…

云服务器实际内存与购买不足量问题

君衍 一、本篇缘由二、问题研究1、dmidecode2、dmesg | grep -i memory 三、kdump四、解决方案1、卸载kdump-tools2、清理依赖包3、修改配置文件4、重新生成配置文件5、重启服务器6、再次查看 一、本篇缘由 本篇由于最近买了云服务器&#xff0c;之前基本在本地使用VMware进行虚…

初识单片机之点亮LED灯

1、前言 如果说编程的开始是Hello world,那么单片机的开始就是点亮LED灯&#xff0c;这个操作最直接的展示了单片机强大的控制功能&#xff0c;这里我就以直接点亮指定位置的LED灯的形式演示这个功能。 2、原理介绍 我的单片机的LED灯都是接在单片机的P1口&#xff0c;从P10~P…

【数据结构(邓俊辉)学习笔记】高级搜索树02——B树

文章目录 1. 大数据1.1 640 KB1.2 越来越大的数据1.3 越来越小的内存1.4 一秒与一天1.5 分级I/O1.6 1B 1KB 2. 结构2.1 观察体验2.2 多路平衡2.3 还是I/O2.4 深度统一2.5 阶次含义2.6 紧凑表示2.7 BTNode2.8 BTree 3. 查找3.1 算法过程3.2 操作实例3.3 算法实现3.4 主次成本3.…

YOLOv8白皮书-第Y8周:yolov8.yaml文件解读

本文为365天深度学习训练营中的学习记录博客 原作者&#xff1a;K同学啊|接辅导、项目定制 请根据YOLOv8n、YOLOv8s模型的结构输出&#xff0c;手写出YOLOv8l的模型输出 文件位置&#xff1a;./ultralytics/cfg/models/v8/yolov8.yaml 一、参数配置 # Parameters nc: 80 # n…

Bug:时间字段显示有问题

Bug&#xff1a;时间字段显示有问题 文章目录 Bug&#xff1a;时间字段显示有问题1、问题2、解决方法一&#xff1a;添加注解3、解决方法二&#xff1a;消息转换器自定义对象映射器配置消息转换器 1、问题 ​ 在后端传输时间给前端的时候&#xff0c;发现前端的时间显示有问题…

设计模式总结(设计模式的原则及分类)

1.什么是设计模式&#xff1f; 设计模式(Design pattern)代表了最佳的实践&#xff0c;通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结…

自动化任务调度,轻松管理海量数据采集项目

摘要&#xff1a; 本文深入探讨了在数据驱动时代&#xff0c;如何通过自动化任务调度轻松驾驭海量数据采集项目&#xff0c;提升工作效率&#xff0c;确保数据处理的准确性和及时性。我们分享了一系列实用策略与工具推荐&#xff0c;帮助企业和开发者优化数据采集流程&#xf…

SQL 中的 EXISTS 子句:探究其用途与应用

目录 EXISTS 子句简介语法 EXISTS 与 NOT EXISTSEXISTS 子句的工作原理实际应用场景场景一&#xff1a;筛选存在关联数据的记录场景二&#xff1a;优化查询性能 EXISTS 与其他 SQL 结构的比较EXISTS vs. JOINEXISTS vs. IN 多重 EXISTS 条件在 UPDATE 语句中使用 EXISTS常见问题…

基于 AntV F2 的雷达图组件开发

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于 AntV F2 的雷达图组件开发 应用场景介绍 雷达图是一种多变量统计图表&#xff0c;用于可视化展示多个维度的数据。它通常用于比较不同对象的多个属性或指标&#xff0c;直观地反映各维度之间的差异和整体…

LoRaWAN协议

目录 一、介绍 1、LPWA是什么&#xff1f; 2、LoRa是什么&#xff1f; 3、LoRaWAN是什么&#xff1f; 4、浅谈LoRa与LoRaWAN的区别 5、LoRaWAN开发背景 6、LoRaWAN与NB-IOT如何选择&#xff1f; 二、LoRaWAN网络结构 1、组网结构 2、星型拓扑结构 三、数据格式 1、…

golang AST语法树解析

1. 源码示例 package mainimport ("context" )// Foo 结构体 type Foo struct {i int }// Bar 接口 type Bar interface {Do(ctx context.Context) error }// main方法 func main() {a : 1 }2. Golang中的AST golang官方提供的几个包&#xff0c;可以帮助我们进行A…

代码随想录算法训练营第五十五天|101.孤岛的总面积、102.沉没孤岛、103.水流问题、104.建造最大岛屿

101.孤岛的总面积 题目链接&#xff1a;101.孤岛的总面积沉没孤岛 文档讲解&#xff1a;代码随想录 状态&#xff1a;不会 思路&#xff1a; 步骤1&#xff1a;将边界上的陆地变为海洋 步骤2&#xff1a;计算孤岛的总面积 题解&#xff1a; public class Main {// 保存四个方…

【UE5.1】NPC人工智能——02 NPC移动到指定位置

效果 步骤 1. 新建一个蓝图&#xff0c;父类选择“AI控制器” 这里命名为“BP_NPC_AIController”&#xff0c;表示专门用于控制NPC的AI控制器 2. 找到我们之前创建的所有NPC的父类“BP_NPC” 打开“BP_NPC”&#xff0c;在类默认值中&#xff0c;将“AI控制器类”一项设置为“…

动手学深度学习——3.多层感知机

1.线性模型 线性模型可能出错 例如&#xff0c;线性意味着单调假设&#xff1a; 任何特征的增大都会导致模型输出的增大&#xff08;如果对应的权重为正&#xff09;&#xff0c; 或者导致模型输出的减小&#xff08;如果对应的权重为负&#xff09;。 有时这是有道理的。 例…

R绘制Venn图及其变换

我自己在用R做各种分析时有不少需要反复用到的基础功能&#xff0c;比如一些简单的统计呀&#xff0c;画一些简单的图等等&#xff0c;虽说具体实现的代码也不麻烦&#xff0c;但还是不太想每次用的时候去找之前的代码。 索性将常用的各种函数整成了一个包&#xff1a;pcutils…

前端JS特效第34集:jQuery俩张图片局部放大预览插件

jQuery俩张图片局部放大预览插件&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Co…

数据结构与算法02迭代|递归

目录 一、迭代(iteration) 1、for循环 2、while循环 二、递归&#xff08;recursion&#xff09; 1、普通递归 2、尾递归 3、递归树 三、对比 简介&#xff1a;在算法中&#xff0c;重复执行某个任务是常见的&#xff0c;它与复杂度息息相关&#xff0c;在程序中实现重…

MySQL MVCC原理

全称Multi-Version Concurrency Control&#xff0c;即多版本并发控制&#xff0c;主要是为了提高数据库的并发性能。 1、版本链 对于使用InnoDB存储引擎的表来说&#xff0c;它的聚簇索引记录中都包含两个必要的隐藏列&#xff1a; 1、trx_id&#xff1a;每次一个事务对某条…