Go 提供了一个package(golang.org/x/time/rate),这使我们很方便的对速度进行限制,如我们的api链接,数据库链接,和一些耗费我们资源(机器资源,时间资源,金钱资源)。
安装
go time 的github地址 https://github.com/golang/time
go get -u golang.org/x/time`
或者使用git clone 到gopath目录下
``$GOPATH/src/golang.org/x/time`.
文档地址
https://godoc.org/golang.org/x/time/rate
介绍
Limiter,查看源码可知,内部使用锁机制实现
Limter限制时间的发生频率,采用令牌池的算法实现。这个池子一开始容量为b,装满b个令牌,然后每秒往里面填充r个令牌。
由于令牌池中最多有b个令牌,所以一次最多只能允许b个事件发生,一个事件花费掉一个令牌。
// The methods AllowN, ReserveN, and WaitN consume n tokens.
type Limiter struct {
limit Limit
burst int
mu sync.Mutex
tokens float64
// 最后一次更新令牌桶时间
last time.Time
// 最后一次事件
lastEvent time.Time
}
常用函数
func Every(interval time.Duration) Limit
时间的最小间隔转化为limit
func NewLimiter(r Limit, b int) *Limiter
创建一个Limiter,r为速率,b为允许的最大token令牌数量,即令牌桶的深度
func (lim *Limiter) Wait(ctx context.Context) (err error)
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)
当没有可用或足够的事件时,将阻塞等待,推荐使用wait/waitN
WaitN 阻塞当前直到lim允许n个事件的发生。
- 如果n超过了令牌池的容量大小则报错。
- 如果Context被取消了则报错。
- 如果lim的等待时间超过了Context的超时时间则报错
func (lim *Limiter) ReserveN(now time.Time) *Reservation
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
ReserveN 返回对象Reservation ,标识调用者需要等多久才能等到n个事件发生(意思就是等多久令牌池中至少含有n个令牌)。如果ReserveN 传入的n大于令牌池的容量b,那么返回false. 如果希望根据频率限制等待和降低事件发生的速度而不丢掉事件,就使用这个方法
func (lim *Limiter) AllowN(now time.Time) bool
func (lim *Limiter) AllowN(now time.Time, n int) bool
AllowN标识在时间now的时候,n个事件是否可以同时发生(也意思就是now的时候是否可以从令牌池中取n个令牌)。如果你需要在事件超出频率的时候丢弃或跳过事件,就使用AllowN,否则使用Reserve或Wait.
简单限速器
由于我们都请求不能够丢弃,所有这了我们使用Wait WaitN来开发。
新建一个SimpleLimitConnection,限速为1秒一个令牌,初始为1个令牌
// 我们的限速连器
type SimpleLimitConnection struct {
rateLimiter * rate.Limiter
}
// 新建api链接
func NewSimpleLimitConnection() *SimpleLimitConnection {
return &SimpleLimitConnection{
// 1秒一个令牌,初始桶为5个令牌
rateLimiter:rate.NewLimiter(rate.Limit(1),5),
}
}
模拟两个服务请求,调用rateLimiter.Wait(ctx)
// 用户信息服务:从数据库获取
func (a *SimpleLimitConnection) GetUserInfo(ctx context.Context) error {
if err := a.rateLimiter.Wait(ctx); err != nil {
return err
}
// 处理数据
return nil
}
// 发送短信服务:收费服务
func (a *SimpleLimitConnection) SendMessage(ctx context.Context) error {
if err := a.rateLimiter.Wait(ctx); err != nil {
return err
}
// 处理数据
return nil
}
新建测试,模拟10个请求,GetUserInfo 5次,SendMessage 5次,
func main() {
// 设置log格式
log.SetOutput(os.Stdout)
log.SetFlags(log.Ldate | log.Ltime | log.LUTC)
limitConnection := NewSimpleLimitConnection()
var wg sync.WaitGroup
wg.Add(10)
// 模拟10次获取用户信息请求
for i := 0; i < 5; i++ {
go func() {
defer wg.Done()
err := limitConnection.GetUserInfo(context.Background())
if err != nil {
log.Printf("获取用户信息 err %v", err)
}
log.Printf("获取用户信息")
}()
}
// 模拟5次 发送短信请求
for i := 0; i < 5; i ++ {
go func() {
defer wg.Done()
err := limitConnection.SendMessage(context.Background())
if err != nil {
log.Printf("发送短信 错误 %v ", err)
}
log.Printf("发送短信")
}()
}
wg.Wait()
}
输出:
2019/02/22 19:19:57 发送短信
2019/02/22 19:19:57 获取用户信息
2019/02/22 19:19:57 获取用户信息
2019/02/22 19:19:57 发送短信
2019/02/22 19:19:57 发送短信
2019/02/22 19:19:58 发送短信
2019/02/22 19:19:59 获取用户信息
2019/02/22 19:20:00 获取用户信息
2019/02/22 19:20:01 获取用户信息
2019/02/22 19:20:02 发送短信
Process finished with exit code 0
可以看到,由于初始令牌数为5,所以前5个请求,直接处理,后续每秒钟添加一个令牌,每秒钟处理1次请求
聚合限速器
上面仅是一个基本的限速,如果某一时刻我们的网站做活动呢 或者用户突然增加,这样的限制太单一,可能造成大量的请求等待。如果想增加更多的层次限制,用细粒度的控制,来限制每秒的请求,粗粒度限制每分或者每小时的请求。
聚合限速:组合不同粒度的限速器,为限速器组,使用组来管理。
定义接口
// 定义接口
type RateLimit interface {
Wait(ctx context.Context) error
Limit() rate.Limit
}
聚合限速器接口实现
注意MultiLimter 时,对限速器切片进行排序了,这样获取Limit时,仅取最大限制即可
// 聚合限速器,实现MultiLimter接口
type multiLiter struct {
limiters []RateLimit
}
// 迭代limiters
func (m *multiLiter) Wait(ctx context.Context) error {
for _, l := range m.limiters {
if err := l.Wait(ctx); err != nil {
return err
}
}
return nil
}
// 必须通过 MultiLimter 来获取实例
func (m *multiLiter) Limit() rate.Limit {
return m.limiters[0].Limit()
}
// 返回一个聚合限速器
func MultiLimter(limters ...RateLimit) *multiLiter {
limitBy := func(i, j int) bool {
return limters[i].Limit() < limters[j].Limit()
}
// 对限速器排序,multiLiter.Limit时,取最大限制即可
sort.Slice(limters, limitBy)
return &multiLiter{limiters: limters}
}
定义限速器
apilimit 这里0.1秒一个,初始为10,
数据库限速 1秒一个,初始令牌为2
// 我们的限速连接器
type LimitApiConnection struct {
messageLimit RateLimit
dbLimit RateLimit
apiLimit RateLimit
}
// 新建api链接,测试数据为了演示
func NewLimitConnection() *LimitApiConnection {
return &LimitApiConnection{
messageLimit: MultiLimter(
rate.NewLimiter(Per(2, time.Second), 2), //0.5秒一个令牌
rate.NewLimiter(Per(6, time.Minute), 6), //1分钟6个,10秒一个令牌
),
dbLimit: MultiLimter(
rate.NewLimiter(Per(2, time.Second), 2), // 1秒一个
),
apiLimit: MultiLimter(
rate.NewLimiter(Per(10, time.Second), 10), // 0.1秒一个
),
}
}
// 工具函数,时间单位的事件数
func Per(eventCount int, duration time.Duration) rate.Limit {
return rate.Every(duration / time.Duration(eventCount))
}
服务请求处理
// 用户信息服务:从数据库获取
func (a *LimitApiConnection) GetUserInfo(ctx context.Context) error {
// api链接数限制、数据库链接限制
if err := MultiLimter(a.apiLimit, a.dbLimit).Wait(ctx); err != nil {
return err
}
return nil
}
// 发送短信服务:收费服务
func (a *LimitApiConnection) SendMessage(ctx context.Context) error {
// api链接数限制、发送短信服务链接限制
if err := MultiLimter(a.apiLimit, a.messageLimit).Wait(ctx); err != nil {
return err
}
return nil
}
测试
func main() {
// 设置log格式
log.SetOutput(os.Stdout)
log.SetFlags(log.Ldate | log.Ltime | log.LUTC)
limitConnection := NewLimitConnection()
var wg sync.WaitGroup
wg.Add(20)
// 模拟10次获取用户信息请求
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
err := limitConnection.GetUserInfo(context.Background())
if err != nil {
log.Printf("获取用户信息 err %v", err)
}
log.Printf("获取用户信息")
}()
}
// 模拟10次 发送短信请求
for i := 0; i < 10; i ++ {
go func() {
defer wg.Done()
err := limitConnection.SendMessage(context.Background())
if err != nil {
log.Printf("发送短信 错误 %v ", err)
}
log.Printf("发送短信")
}()
}
wg.Wait()
}
go完整代码
/*
@Time : 2019-02-23 00:48
@Author : suibingyue
@File : main
*/
package main
import (
"context"
"golang.org/x/time/rate"
"log"
"os"
"sort"
"sync"
"time"
)
// 定义接口
type RateLimit interface {
Wait(ctx context.Context) error
Limit() rate.Limit
}
// 聚合限速器,实现MultiLimter接口
type multiLiter struct {
limiters []RateLimit
}
// 迭代limiters
func (m *multiLiter) Wait(ctx context.Context) error {
for _, l := range m.limiters {
if err := l.Wait(ctx); err != nil {
return err
}
}
return nil
}
// 必须通过 MultiLimter 来获取实例
func (m *multiLiter) Limit() rate.Limit {
return m.limiters[0].Limit()
}
// 返回一个聚合限速器
func MultiLimter(limters ...RateLimit) *multiLiter {
limitBy := func(i, j int) bool {
return limters[i].Limit() < limters[j].Limit()
}
// 对限速器排序,multiLiter.Limit时,取最大限制即可
sort.Slice(limters, limitBy)
return &multiLiter{limiters: limters}
}
// 我们的限速连接器
type LimitApiConnection struct {
messageLimit RateLimit
dbLimit RateLimit
apiLimit RateLimit
}
// 新建api链接
func NewLimitConnection() *LimitApiConnection {
return &LimitApiConnection{
messageLimit: MultiLimter(
rate.NewLimiter(Per(2, time.Second), 2), //0.5秒一个令牌
rate.NewLimiter(Per(6, time.Minute), 6), //1分钟6个,10秒一个令牌
),
dbLimit: MultiLimter(
rate.NewLimiter(Per(2, time.Second), 2), // 1秒2个
),
apiLimit: MultiLimter(
rate.NewLimiter(Per(10, time.Second), 10), // 0.1秒一个
),
}
}
// 工具函数,时间单位的事件数
func Per(eventCount int, duration time.Duration) rate.Limit {
return rate.Every(duration / time.Duration(eventCount))
}
// 用户信息服务:从数据库获取
func (a *LimitApiConnection) GetUserInfo(ctx context.Context) error {
// api链接数限制
// 数据库链接限制 1秒一个令牌
if err := MultiLimter(a.apiLimit, a.dbLimit).Wait(ctx); err != nil {
return err
}
return nil
}
// 发送短信服务:收费服务
func (a *LimitApiConnection) SendMessage(ctx context.Context) error {
// api链接数限制
// 发送短信服务链接限制 10秒一个令牌
if err := MultiLimter(a.apiLimit, a.messageLimit).Wait(ctx); err != nil {
return err
}
return nil
}
func main() {
// 设置log格式
log.SetOutput(os.Stdout)
log.SetFlags(log.Ldate | log.Ltime | log.LUTC)
limitConnection := NewLimitConnection()
var wg sync.WaitGroup
wg.Add(20)
// 模拟10次获取用户信息请求
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
err := limitConnection.GetUserInfo(context.Background())
if err != nil {
log.Printf("获取用户信息 err %v", err)
}
log.Printf("获取用户信息")
}()
}
// 模拟10次 发送短信请求
for i := 0; i < 10; i ++ {
go func() {
defer wg.Done()
err := limitConnection.SendMessage(context.Background())
if err != nil {
log.Printf("发送短信 错误 %v ", err)
}
log.Printf("发送短信")
}()
}
wg.Wait()
}
输出
2019/02/22 18:29:17 发送短信
2019/02/22 18:29:17 获取用户信息
2019/02/22 18:29:17 发送短信
2019/02/22 18:29:17 获取用户信息
2019/02/22 18:29:18 获取用户信息
2019/02/22 18:29:18 发送短信
2019/02/22 18:29:18 发送短信
2019/02/22 18:29:18 获取用户信息
2019/02/22 18:29:19 获取用户信息
2019/02/22 18:29:19 发送短信
2019/02/22 18:29:19 获取用户信息
2019/02/22 18:29:19 发送短信
2019/02/22 18:29:20 获取用户信息
2019/02/22 18:29:20 获取用户信息
2019/02/22 18:29:21 获取用户信息
2019/02/22 18:29:21 获取用户信息
2019/02/22 18:29:27 发送短信
2019/02/22 18:29:37 发送短信
2019/02/22 18:29:47 发送短信
2019/02/22 18:29:57 发送短信
分析:
由于api限速为0.1秒一个,固apilimit本测试用例可忽略
发送短信服务 SendMessage
messageLimit: 1分钟6个,10秒一个令牌,默认令牌桶6个令牌,所以从第7次开始,10秒处理一次请求。
获取用户信息服务 GetUserInfo
dbLimit:0.5秒一个令牌,即1秒钟2个令牌,初始令牌桶2个令牌。所以从第三个令牌开始,18秒后,每秒处理2个请求