golang开发聚合限速器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bondsui/article/details/87888005

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个请求

猜你喜欢

转载自blog.csdn.net/bondsui/article/details/87888005