限速算法之滑动窗口
前言
假设作为一个完全不懂算法的人,让你去实现一个限速功能(1秒内最多100次),你可能会想到的最简单方式就是记录一个开始时间,然后开始计数,当计数达到100之后限制调用,等待时间间隔达到1秒的时候重置计数器,然后重新计数,如此往复。
这种方式被称为计数器算法
,也可以理解为固定时间窗口计数法
,因为与之对应的还有一个方法叫作滑动时间窗口计数法
。
package main import ( "fmt" "time" ) func main() { var count = 1 var sTime = time.Now() for { //如果当前时间段超过1秒了,重新计数,并重置开始时间 if time.Now().Sub(sTime).Seconds() > 1 { count = 1 sTime = time.Now() } //如果计数机大于100了,改时间段内就不能再做操作了 if count > 100 { time.Sleep(time.Millisecond * 10) continue } doSomething(count) count++ } } func doSomething(n int) { fmt.Printf("do something #%d\n", n) }
这种算法最大问题在于临界点问题,比如说在上面这个例子里面,假设我们在前面1秒钟中最后999ms的时候打印了100个,又在第二个1秒的前1ms内又打印了100个,那么总得算起来,其在2ms内就打印了200个,还是没有达到我们想要的效果。
假设我要实现10s内有5次通过,看看具体是怎么实现的:
2、第1秒时来了一个事件,因为刚开始size=0,小于5,都没有到限制的次数,完全不用考虑时间窗口,可以直接把这次事件的时间戳放到0的位置 :
3、第2.8秒的时候,第二个事件来了。因为此时size=1,还是小于5,把这次事件的时间戳放到0的位置,原来第1秒来的事件时间戳会往后移动一格:
4、陆续的又来了3个事件,队列大小变成了5,先来的时间戳依次向后移动。此时,第6个事件来了,时间是第8秒:
5、因为size=5,不小于5,此时已经达到限制次数,以后都需要考虑时间窗口了。所以取出位置4的时间(离现在最远的时间),和第6个事件的时间戳做比较:
8、第11.1秒,第101次事件过来了。因为size=5,不小于5,所以取出位置4的时间(离现在最远的时间),和第101个事件的时间戳做比较:
往后再来其他事件,就是重复4-10的步骤,即可实现,在任意滑动时间窗口内,限制通过的次数
测试代码:
package utils import "time" var LimitQueue map[string][]int64 var ok bool //单机时间滑动窗口限流法 func LimitFreqSingle(queueName string, count uint, timeWindow int64) bool { currTime := time.Now().Unix() if LimitQueue == nil { LimitQueue = make(map[string][]int64) } if _, ok = LimitQueue[queueName]; !ok { LimitQueue[queueName] = make([]int64, 0) } //队列未满 if uint(len(LimitQueue[queueName])) < count { LimitQueue[queueName] = append(LimitQueue[queueName], currTime) return true } //队列满了,取出最早访问的时间 earlyTime := LimitQueue[queueName][0] //说明最早期的时间还在时间窗口内,还没过期,所以不允许通过 if currTime-earlyTime <= timeWindow { return false } else { //说明最早期的访问应该过期了,去掉最早期的 LimitQueue[queueName] = LimitQueue[queueName][1:] LimitQueue[queueName] = append(LimitQueue[queueName], currTime) } return true }
func limitIpFreq(c *gin.Context, timeWindow int64, count uint) bool { ip := c.ClientIP() key := "limit:" + ip if !utils.LimitFreqSingle(key, count, timeWindow) { c.JSON(200, gin.H{ "code": 400, "msg": "error Current IP frequently visited", }) return false } return true }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性