Go并发模式/设计模式之速率限制(API速率限制)

速率限制;它限制了某种资源在某段时间内 被访问的次数。 资源可以是任何东西: API连接, 磁盘读写, 网络包, 异常。

通常情况下 用户对系统的访问应当 被沙盒化,既不会影响其他用户的活动, 也不会受到其他用户的影响。

访问 收费系统 时, 速率限制可以使你与客户保持良好的关系。Google 的云服务中 就用到了 速率限制。

Go语言 中 如何 进行限速呢?
大多数的限速 是基于 令牌桶算法的。 要想访问资源,必须拥有资源的访问令牌,没有令牌的请求会被拒绝。
桶的深度为 d ;代表可以存放 d个令牌。

先看一个没有使用限速 的例子:



type APIConnection struct{}

func Open() *APIConnection{
	return &APIConnection{}
}

func (a *APIConnection) ReadFile(ctx context.Context) error{
	//执行一些操作
	return nil
}

func (a *APIConnection) ResolveAddress(ctx context.Context) error{
	//执行一些操作
	return nil
}

func main() {
	defer log.Printf("Done.")
	log.SetOutput(os.Stdout)
	log.SetFlags(log.Ltime | log.LUTC)

	apiConnection := Open()
	var wg sync.WaitGroup
	wg.Add(20)

	for i:=0 ; i<10; i++{
		go func() {
			defer wg.Done()
			err := apiConnection.ReadFile(context.Background())
			if err != nil{
				log.Printf("cannot ReadFile: %v", err)
			}
			log.Printf("ReadFile")
		}()
	}

	for i:=0; i<10; i++{
		go func() {
			defer wg.Done()
			err := apiConnection.ResolveAddress(context.Background())
			if err != nil{
				log.Printf("cannot ResolveAddress: %v", err)
			}
			log.Printf("ResolveAddress")
		}()
	}

	wg.Wait()
}
打算把 限速 放在 APIConnection 中,但通常限速器会在服务器上运行,这可以防止用户轻易的绕过它。

使用 golang.org/x/time/rate 中 令牌桶限速器实现。

创建一个Limiter
func NewLimiter(r Limit, b int) *Limiter
使用Limiter 的 Wait 来 阻塞我们的请求,直到获得访问令牌。
func Open() *APIConnection{
	return &APIConnection{
		rateLimiter: rate.NewLimiter(rate.Limit(1), 1),
	}
}

type APIConnection struct{
	rateLimiter *rate.Limiter
}

func (a *APIConnection) ReadFile(ctx context.Context) error{
	if err := a.rateLimiter.Wait(ctx); err != nil{
		return err
	}
	//在这里执行一些逻辑
	return nil
}

func (a *APIConnection) ResolveAddress(ctx context.Context) error{
	if err := a.rateLimiter.Wait(ctx); err != nil{
		return err
	}

	//在这里执行一些逻辑
	return nil
}
可能 会想要建立多层次的限制:用细粒度的控制 来限制每秒的请求,用粗粒度的控制来限制每分钟,每小时 或每天的请求。

在某些情况下,可以用单一的 限速器 来做到这一点。然而并不能适应所有情况,

所以呢,把不同粒度的限速器独立,然后将它们组合成一个限速器组 来管理 会更容易达到目的。
type RateLimiter interface {
	Wait(ctx context.Context) error
	Limit() rate.Limit
}

type multiLimiter struct{
	limiters []RateLimiter
}

func MultiLimiter(limiters ...RateLimiter) *multiLimiter{
	byLimit := func(i, j int) bool {
		return limiters[i].Limit() < limiters[j].Limit()
	}
	sort.Slice(limiters, byLimit)
	return &multiLimiter{limiters:limiters}

}

func (l *multiLimiter) Wait(ctx context.Context) error{
	for _, l := range l.limiters {
		if err := l.Wait(ctx); err != nil{
			return err
		}
	}
	return nil
}

//速度上 以 最慢的 那个 返回了,针对下面的 例子 就是 每分钟十个 返回了
func (l *multiLimiter) Limit() rate.Limit{
	return l.limiters[0].Limit()
}

func Per(eventCount int, duration time.Duration) rate.Limit{
	return rate.Every(duration/time.Duration(eventCount))
}



type APIConnection struct{
	rateLimiter RateLimiter
}

func (a *APIConnection) ReadFile(ctx context.Context) error{
	if err := a.rateLimiter.Wait(ctx); err != nil{
		return err
	}
	//执行一些逻辑
	return nil
}

func (a *APIConnection) ResolveAddress(ctx context.Context) error{
	if err := a.rateLimiter.Wait(ctx); err != nil{
		return err
	}
	//执行一些逻辑
	return nil
}

func Open() *APIConnection {
	//这里定义 每秒 的限制, 避免突发请求
	secondLimit := rate.NewLimiter(Per(2, time.Second),1)
	//这里 定义每分钟的 限制, 为用户提供初始池。 每秒的限制 将确保我们的系统 不会因为突发的请求 而超载
	minuteLimit := rate.NewLimiter(Per(10, time.Minute), 10)
	return &APIConnection{
		rateLimiter: MultiLimiter(secondLimit, minuteLimit),
	}
}

//10:44:19 ReadFile
//10:44:19 ReadFile
//10:44:20 ReadFile
//10:44:20 ResolveAddress
//10:44:21 ReadFile
//10:44:21 ResolveAddress
//10:44:22 ResolveAddress
//10:44:22 ResolveAddress
//10:44:23 ResolveAddress
//10:44:23 ResolveAddress
//10:44:25 ResolveAddress
//10:44:31 ResolveAddress
//10:44:37 ResolveAddress
//10:44:43 ReadFile
//10:44:49 ReadFile
//10:44:55 ReadFile
//10:45:01 ReadFile
//10:45:07 ReadFile
//10:45:13 ReadFile
//10:45:19 ResolveAddress
//10:45:19 Done.
分析:
 客户端 每秒 发出两个请求。 直到第十一个请求,我们开始每6秒种 发出一次请求。 这是因为 耗尽了 每分钟限速器的可用令牌,所以限制了请求速度。


除了考虑时间维度, 还有可能 使用其他维度来限制。 如:除了对API请求的数量有一些限制, 同时也可能对其他资源(如磁盘访问,网络访问等)有限制。
type APIConnection struct{
	networkLimit,
	diskLimit,
	apiLimit RateLimiter
}

func Open() *APIConnection{
	return &APIConnection{
		apiLimit: MultiLimiter(
			rate.NewLimiter(Per(2, time.Second), 2),
			rate.NewLimiter(Per(10, time.Minute), 10),
		),
		diskLimit:MultiLimiter(
			rate.NewLimiter(rate.Limit(1), 1),
		),
		networkLimit:MultiLimiter(
			rate.NewLimiter(Per(3, time.Second), 3),
		),
	}
}

func (a *APIConnection) ReadFile(ctx context.Context) error{
	err := MultiLimiter(a.apiLimit, a.diskLimit).Wait(ctx)
	if err != nil {
		return err
	}

	//这里执行一些逻辑
	return nil
}


func (a *APIConnection) ResolveAddress(ctx context.Context) error{
	err := MultiLimiter(a.apiLimit, a.networkLimit).Wait(ctx)
	if err != nil{
		return err
	}
	//这里执行一些逻辑
	return nil
}

//12:18:33 ReadFile
//12:18:33 ResolveAddress
//12:18:33 ResolveAddress
//12:18:34 ReadFile
//12:18:35 ReadFile
//12:18:35 ResolveAddress
//12:18:35 ResolveAddress
//12:18:36 ResolveAddress
//12:18:36 ReadFile
//12:18:37 ReadFile
//12:18:39 ReadFile
//12:18:45 ReadFile
//12:18:51 ResolveAddress
//12:18:57 ReadFile
//12:19:03 ResolveAddress
//12:19:09 ResolveAddress
//12:19:15 ResolveAddress
//12:19:21 ReadFile
//12:19:27 ResolveAddress
//12:19:33 ReadFile
//12:19:33 Done.

/**

rate.Limiter 还有一些特殊 技巧,遇到时在进一步研究

rate.Limiter 还有一些特殊 技巧,遇到时在进一步研究

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值