高并发系统设计 -- 服务限流算法

常见的限流算法

漏桶算法

img

漏桶法的关键点在于漏桶始终按照固定的速率运行,但是它并不能很好的处理有大量突发请求的场景,毕竟在某些场景下我们可能需要提高系统的处理效率,而不是一味的按照固定速率处理请求。

关于漏桶的实现,uber团队有一个开源的github.com/uber-go/ratelimit库。 这个库的使用方法比较简单,Take() 方法会返回漏桶下一次滴水的时间。

package main

import (
   "fmt"
   "time"

   "go.uber.org/ratelimit"
)

func main() {
   r1 := ratelimit.New(10) // (10表示1s/10,即每100ms(0.1s)运行一次;同理,1表示1s/1,即每秒运行一次)
   prev := time.Now()
   for i := 0; i < 10; i++ {
      now := r1.Take()
      fmt.Println(i, now.Sub(prev))
      prev = now
   }
}

令牌桶算法

令牌桶(Token bucket)其实和漏桶的原理类似,令牌桶按固定的速率往桶里放入令牌,并且只要能从桶里取出令牌就能通过令牌桶支持突发流量的快速处理

img

对于从桶里取不到令牌的场景,我们可以选择等待也可以直接拒绝并返回。

对于令牌桶的Go语言实现,大家可以参照github.com/juju/ratelimit库。这个库支持多种令牌桶模式,并且使用起来也比较简单。

创建令牌桶的方法:

// 创建指定填充速率和容量大小的令牌桶
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
// 创建指定填充速率、容量大小和每次填充的令牌数的令牌桶
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
// 创建填充速度为指定速率和容量大小的令牌桶
// NewBucketWithRate(0.1, 200) 表示每秒填充20个令牌
func NewBucketWithRate(rate float64, capacity int64) *Bucket

取出令牌的方法如下:

// 取token(非阻塞)
func (tb *Bucket) Take(count int64) time.Duration
func (tb *Bucket) TakeAvailable(count int64) int64

// 最多等maxWait时间取token
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool)

// 取token(阻塞)
func (tb *Bucket) Wait(count int64)
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool

虽说是令牌桶,但是我们没有必要真的去生成令牌放到桶里,我们只需要每次来取令牌的时候计算一下,当前是否有足够的令牌就可以了,具体的计算方式可以总结为下面的公式:

当前令牌数 = 上一次剩余的令牌数 + (本次取令牌的时刻-上一次取令牌的时刻)/放置令牌的时间间隔 * 每次放置的令牌数

我们推荐使用令牌桶算法。

gin中使用限流中间件

import (
   "github.com/gin-gonic/gin"
   "github.com/juju/ratelimit"
   "net/http"
   "time"
)

func RateLimit() gin.HandlerFunc {
   // 每100ms填充一个令牌,令牌桶容量为10000
   bucket := ratelimit.NewBucket(time.Microsecond*100, int64(10000))
   return func(c *gin.Context) {
      // 如果获取不到令牌就中断本次请求返回
      if bucket.TakeAvailable(1) < 1 {
         c.JSON(http.StatusOK, gin.H{
            "msg": "rate limit...",
         })
         c.Abort()
         return
      }
   }
}

我们可以看到这个中间件里面使用了c.Abort这一个代码,所以接下来我们来讲解一下c.Abort

c.Next和c.Abort

Next():在当前中间件中调用c.Next()时会中断当前中间件中后续的逻辑转而执行后续的中间件和handlers,等它们全部执行完之后再回来执行当前中间件的后续代码。

结论

  • c.Next()只针对当前的中间件,并不影响其他中间件。如果在中间件函数的非结尾调用Next()方法当前中间件剩余代码会被暂停执行,会先去执行后续中间件及handlers,等这些handlers全部执行完以后程序控制权会回到当前中间件继续执行剩余代码;
  • 如果想中断剩余中间件以及handlers应该使用Abort方法,但需要注意当前中间件的剩余代码会继续执行。
  • 如果想提前中止当前中间件的执行应该使用return退出而不是Next()方法;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡桃姓胡,蝴蝶也姓胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值