golang:时间窗口法实现限流器

Posted IGuoSJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang:时间窗口法实现限流器相关的知识,希望对你有一定的参考价值。

package main

import (
	"container/list"
	"fmt"
	"sync"
	"time"
)

/*
 Author: Guo
 Date: 3/15/21 4:58 PM
 Description:
 Company: 
 Updated: 姓名@时间@版本 变更说明
*/

// 限量器
type Limitor struct 
	// 锁
	Lock sync.Mutex
	// 存储元素的双向链表
	Elements *list.List
	// 最多可以保存的元素个数
	MaxNum int
	// 时间窗口的长度
	TimeWindow time.Duration


// 构造方法
func NewLimitor(maxNum int, timeW time.Duration) *Limitor 
	return &Limitor
		//Lock: sync.Mutex,
		Elements:   list.New(),
		MaxNum:     maxNum,
		TimeWindow: timeW,
	


func IsFullOrAdd(limitor *Limitor, t time.Time) bool 
	limitor.Lock.Lock()
	defer limitor.Lock.Unlock()
	// 如果没超过最大限度就存储
	if limitor.Elements.Len() < limitor.MaxNum 
		limitor.Elements.PushBack(t)
		return false
	 else 
		// 否则就要删除老旧的元素

		// 取出链表的头元素,也就是最早加入到链表的元素
		tmp := limitor.Elements.Front()
		for tmp != nil 
			// 如果最早加入到链表的元素在时间窗口内,说明没有元素可以删除,已满
			if time.Now().Add(-1 * limitor.TimeWindow).Before(tmp.Value.(time.Time)) 
				return true
			 else 
				// 否则删除掉头元素
				limitor.Elements.Remove(limitor.Elements.Front())
				// 如果此时链表里有空间了,就加入
				if limitor.Elements.Len() < limitor.MaxNum 
					limitor.Elements.PushBack(t)
					return false
				
				// 否则头指针前移,再来一次
				tmp = limitor.Elements.Front()
			
		
	
	return true


func main() 
	limitOr := NewLimitor(5, 10*time.Second)
	for 
		t := time.Now()
		if IsFullOrAdd(limitOr, t) 
			break
		
		fmt.Println(t)
		time.Sleep(time.Second)
	
	time.Sleep(time.Second * 10)

	if IsFullOrAdd(limitOr, time.Now()) 
		fmt.Println("full")
	 else 
		fmt.Println("saved")
	

	tmp := limitOr.Elements.Front()
	for tmp != nil 
		fmt.Println(tmp.Value.(time.Time))
		limitOr.Elements.Remove(tmp)
		tmp = limitOr.Elements.Front()
	

控制台输出结果:

2021-03-15 19:16:47.861605539 +0800 HKT m=+0.000073557
2021-03-15 19:16:48.861766058 +0800 HKT m=+1.000234300
2021-03-15 19:16:49.862036845 +0800 HKT m=+2.000505121
2021-03-15 19:16:50.862195606 +0800 HKT m=+3.000663861
2021-03-15 19:16:51.862422857 +0800 HKT m=+4.000891158
saved
2021-03-15 19:16:48.861766058 +0800 HKT m=+1.000234300
2021-03-15 19:16:49.862036845 +0800 HKT m=+2.000505121
2021-03-15 19:16:50.862195606 +0800 HKT m=+3.000663861
2021-03-15 19:16:51.862422857 +0800 HKT m=+4.000891158
2021-03-15 19:17:02.862979158 +0800 HKT m=+15.001447425

可以看到最早加入的元素被删除了。
这种情况下,在队列满了之后,队列里面始终会有五个元素。
还有另外一种实现方式:

package main

import (
	"container/list"
	"fmt"
	"sync"
	"time"
)

/*
 Author: Guo
 Date: 3/15/21 4:58 PM
 Description:
 Company: 
 Updated: 姓名@时间@版本 变更说明
*/

// 限量器
type Limitor struct 
	// 锁
	Lock sync.Mutex
	// 存储元素的双向链表
	Elements *list.List
	// 最多可以保存的元素个数
	MaxNum int
	// 时间窗口的长度
	TimeWindow time.Duration


// 构造方法
func NewLimitor(maxNum int, timeW time.Duration) *Limitor 
	return &Limitor
		//Lock: sync.Mutex,
		Elements:   list.New(),
		MaxNum:     maxNum,
		TimeWindow: timeW,
	


func IsFullOrAdd(limitor *Limitor, t time.Time) bool 
	limitor.Lock.Lock()
	defer limitor.Lock.Unlock()
	// 如果没超过最大限度就存储
	if limitor.Elements.Len() < limitor.MaxNum 
		limitor.Elements.PushBack(t)
		return false
	 else 
		// 否则就要删除老旧的元素

		// 取出链表的头元素,也就是最早加入到链表的元素
		tmp := limitor.Elements.Front()
		for tmp != nil 
			// 如果最早加入到链表的元素在时间窗口内,说明没有元素可以删除,已满
			if time.Now().Add(-1 * limitor.TimeWindow).Before(tmp.Value.(time.Time)) 
				break
			 else 
				// 否则删除掉头元素
				limitor.Elements.Remove(limitor.Elements.Front())
				// 头指针前移,再来一次
				tmp = limitor.Elements.Front()
			
		
		// 如果此时链表里有空间了,就加入
		if limitor.Elements.Len() < limitor.MaxNum 
			limitor.Elements.PushBack(t)
			return false
		
	
	return true


func main() 
	limitOr := NewLimitor(5, 10*time.Second)
	for 
		t := time.Now()
		if IsFullOrAdd(limitOr, t) 
			break
		
		fmt.Println(t)
		time.Sleep(time.Second)
	
	time.Sleep(time.Second * 10)

	if IsFullOrAdd(limitOr, time.Now()) 
		fmt.Println("full")
	 else 
		fmt.Println("saved")
	

	tmp := limitOr.Elements.Front()
	for tmp != nil 
		fmt.Println(tmp.Value.(time.Time))
		limitOr.Elements.Remove(tmp)
		tmp = limitOr.Elements.Front()
	

这时控制台输出结果:

2021-03-15 19:26:59.970839953 +0800 HKT m=+0.000070166
2021-03-15 19:27:00.971275169 +0800 HKT m=+1.000505762
2021-03-15 19:27:01.971694545 +0800 HKT m=+2.000924876
2021-03-15 19:27:02.971869634 +0800 HKT m=+3.001099985
2021-03-15 19:27:03.972041835 +0800 HKT m=+4.001272199
saved
2021-03-15 19:27:14.972562803 +0800 HKT m=+15.001793159

可以看到,这次队列里面所有超期的元素都被删除掉了。

以上是关于golang:时间窗口法实现限流器的主要内容,如果未能解决你的问题,请参考以下文章

Golang 限流器 time/rate 使用介绍

使用golang实现令牌桶限流和时间窗口控制

使用golang实现令牌桶限流和时间窗口控制

Guava-RateLimiter实现令牌桶控制接口限流方案

十四:Sentinel核心架构源码剖析

主流的几种限流策略,我都可以通过python+redis实现