golang高并发系统限流策略漏桶和令牌桶算法源码剖析

目录
  • 前言
  • 漏桶算法
    • 样例
    • 源码实现
  • 令牌桶算法
    • 样例
  • 源码剖析
    • Limit类型
    • Limiter结构体
    • Reservation结构体
    • Limiter消费token
    • limiter归还Token
  • 总结

前言

今天与大家聊一聊高并发系统中的限流技术,限流又称为流量控制,是指限制到达系统的并发请求数,当达到限制条件则可以拒绝请求,可以起到保护下游服务,防止服务过载等作用。常用的限流策略有漏桶算法、令牌桶算法、滑动窗口;下文主要与大家一起分析一下漏桶算法和令牌桶算法,滑动窗口就不在这里这介绍了。好啦,废话不多话,开整。

文中测试代码已上传:github.com/asong2020/G…

漏桶算法

漏桶算法比较好理解,假设我们现在有一个水桶,我们向这个水桶里添水,虽然我们我们无法预计一次会添多少水,也无法预计水流入的速度,但是可以固定出水的速度,不论添水的速率有多大,都按照固定的速率流出,如果桶满了,溢出的上方水直接抛弃。我们把水当作HTTP请求,每次都把请求放到一个桶中,然后以固定的速率处理请求,说了这么多,不如看一个图加深理解(图片来自于网络,手残党不会画,多多包涵):

原理其实很简单,就看我们怎么实现它了,uber团队有一个开源的uber-go/ratelimit库,这个库就是漏桶的一种实现,下面我们一起来看一看他的实现思路。

样例

学习一个新东西的时候,往往是从会用开始的,慢慢才能明白其实现原理,所以我们先来看看这个库是怎样使用的,这里我们直接提供一个实际使用例子,配合Gin框架,我们添加一个限流中间件,来达到请求限流的作用,测试代码如下:

// 定义全局限流器对象
var rateLimit ratelimit.Limiter
// 在 gin.HandlerFunc 加入限流逻辑
func leakyBucket() gin.HandlerFunc {
	prev := time.Now()
	return func(c *gin.Context) {
		now := rateLimit.Take()
		fmt.Println(now.Sub(prev)) // 为了打印时间间隔
		prev = now // 记录上一次的时间,没有这个打印的会有问题
	}
}
func main() {
	rateLimit = ratelimit.New(10)
	r := gin.Default()
	r.GET("/ping", leakyBucket(), func(c *gin.Context) {
		c.JSON(200, true)
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

我们简单使用压测工具ab测试一下:ab -n 10 -c 2 http://127.0.0.1:8080/ping,执行结果部分如下:

观察结果可知,每次处理请求的时间间隔是10ms,并且后面的请求耗时越来越久,为什么会这样呢? 这里先卖个小关子,看完uber的实现你就知道了~

源码实现

我们首先来看一下其核心结构:

type limiter struct {
	sync.Mutex
	last       time.Time
	sleepFor   time.Duration
	perRequest time.Duration
	maxSlack   time.Duration
	clock      Clock
}
type Limiter interface {
	// Take should block to make sure that the RPS is met.
	Take() time.Time
}

限制器接口只提供了一个方法take(),take()方法会阻塞确保两次请求之间的时间走完,具体实现我们在下面进行分析。实现限制器接口的结构体中各个字段的意义如下:

sync.Mutext:互斥锁,控制并发的作用

last:记录上一次的时刻

sleepFor:距离处理下一次请求需要等待的时间

perRequest:每次请求的时间间隔

maxSlack:最大松弛量,用来解决突发流量

clock:一个时钟或模拟时钟,提供了now和sleep方法,是实例化速率限制器

要是用该限制器,首先需要通过New方法进行初始化,一个必传的参数是rate,代表的是每秒请求量(RPS),还有一个可选参数,参数类型option,也就是我们可以自定义limit,不过一般使用场景不多,这里就不过多介绍了。我主要看一下他是怎么保证固定速率的,截取New方法部分代码如下:

l := &limiter{
		perRequest: time.Second / time.Duration(rate),
		maxSlack:   -10 * time.Second / time.Duration(rate),
	}

根据我们传入的请求数量,能计算出1s内要通过n个请求,每个请求之间的间隔时间是多少,这样在take方法中就可以根据这个字段来处理请求的固定速率问题,这里还初始化了最大松弛化字段,他的值是负数,默认最大松弛量是10个请求的时间间隔。

接下来我们主要看一下take方法:

func (t *limiter) Take() time.Time {
	t.Lock()
	defer t.Unlock()
	now := t.clock.Now()
	if t.last.IsZero() {
		t.last = now
		return t.last
	}
	t.sleepFor += t.perRequest - now.Sub(t.last)
	if t.sleepFor < t.maxSlack {
		t.sleepFor = t.maxSlack
	}
	if t.sleepFor > 0 {
		t.clock.Sleep(t.sleepFor)
		t.last = now.Add(t.sleepFor)
		t.sleepFor = 0
	} else {
		t.last = now
	}
	return t.last
}

take()方法的执行步骤如下:

  • 为了控制并发,所以进入该方法就需要进行上锁,该锁的粒度比较大,整个方法都加上了锁
  • 通过IsZero方法来判断当前是否是第一次请求,如果是第一次请求,直接取now时间即可返回。
  • 如果不是第一次请求,就需要计算距离处理下一次请求需要等待的时间,这里有一个要注意点的是累加需要等待的时间,目的是可以给后面的抵消使用
  • 如果当前累加需要等待的时间大于最大松弛量了,将等待的时间设置为最大松弛量的时间。
  • 如果当前请求多余的时间无法完全抵消此次的所需量,调用sleep方法进行阻塞,同时清空等待的时间。如果sleepFor小于0,说明此次请求时间间隔大于预期间隔,也就说无需等待可以直接处理请求。

步骤其实不是很多,主要需要注意一个知识点 —— 最大松弛量。

漏桶算法有个天然缺陷就是无法应对突发流量(匀速,两次请求 req1 和 req2 之间的延迟至少应该 >=perRequest),举个例子说明:假设我们现在有三个请求req1、req2、req3按顺序处理,每个请求处理间隔为100ms,req1请求处理完成之后150ms,req2请求到来,依据限速策略可以对 req2 立即处理,当 req2 完成后,50ms 后, req3 到来,这个时候距离上次请求还不足 100ms,因此还需要等待 50ms 才能继续执行, 但是,对于这种情况,实际上这三个请求一共消耗了 250ms 才完成,并不是预期的 200ms。

对于上面这种情况,我们可以把之前间隔比较长的请求的时间匀给后面的请求判断限流时使用,减少请求等待的时间了,但是当两个请求之间到达的间隔比较大时,就会产生很大的可抵消时间,以至于后面大量请求瞬间到达时,也无法抵消这个时间,那样就已经失去了限流的意义,所以引入了最大松弛量 (maxSlack) 的概念, 该值为负值,表示允许抵消的最长时间,防止以上情况的出现。

以上就是漏桶实现的基本思路了,整体还是很简单的,你学会了吗?

令牌桶算法

令牌桶其实和漏桶的原理类似,令牌桶就是想象有一个固定大小的桶,系统会以恒定速率向桶中放 Token,桶满则暂时不放。从网上找了图,表述非常恰当:

关于令牌桶限流算法的实现,Github有一个高效的基于令牌桶限流算法实现的限流库:github.com/juju/ratelimit,Golang的timer/rate也是令牌桶的一种实现,本文就不介绍juju/ratelimit库了,有兴趣的自己学习一下的他的实现思想吧,我们主要来看一看time/rate是如何实现的。

样例

还是老样子,我们还是结合gin写一个限流中间件看看他是怎么使用的,例子如下:

import (
	"net/http"
	"time"
	"github.com/gin-gonic/gin"
	"golang.org/x/time/rate"
)
var rateLimit *rate.Limiter
func tokenBucket() gin.HandlerFunc {
	return func(c *gin.Context) {
		if rateLimit.Allow() {
			c.String(http.StatusOK, "rate limit,Drop")
			c.Abort()
			return
		}
		c.Next()
	}
}
func main() {
	limit := rate.Every(100 * time.Millisecond)
	rateLimit = rate.NewLimiter(limit, 10)
	r := gin.Default()
	r.GET("/ping", tokenBucket(), func(c *gin.Context) {
		c.JSON(200, true)
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

上面的例子我们首先调用NewLimiter方法构造一个限流器,第一个参数是r limit,代表每秒可以向Token桶中产生多少token,第二个参数是b int,代表Token桶的容量大小,对于上面的例子,表示每100ms往桶中放一个token,也就是1s钟产生10个,桶的容量就是10。消费token的方法这里我们使用Allow方法,Allow 实际上就是 AllowN(time.Now(),1),AllowN 方法表示,截止到某一时刻,目前桶中数目是否至少为 n 个,满足则返回 true,同时从桶中消费 n 个 token。反之返回不消费 Token。对应上面的例子,当桶中的数目不足于1个时,就会丢掉该请求。

源码剖析

Limit类型

time/rate自定义了一个limit类型,其实他本质就是float64的别名,Limit定了事件的最大频率,表示每秒事件的数据量,0就表示无限制。Inf是无限的速率限制;它允许所有事件(即使突发为0)。还提供 Every 方法来指定向 Token 桶中放置 Token 的间隔,计算出每秒时间的数据量。

type Limit float64
// Inf is the infinite rate limit; it allows all events (even if burst is zero).
const Inf = Limit(math.MaxFloat64)
// Every converts a minimum time interval between events to a Limit.
func Every(interval time.Duration) Limit {
	if interval &lt;= 0 {
		return Inf
	}
	return 1 / Limit(interval.Seconds())
}

Limiter结构体

type Limiter struct {
	mu     sync.Mutex
	limit  Limit
	burst  int
	tokens float64
	// last is the last time the limiter's tokens field was updated
	last time.Time
	// lastEvent is the latest time of a rate-limited event (past or future)
	lastEvent time.Time
}

各个字段含义如下:

  • mu:互斥锁、为了控制并发
  • limit:每秒允许处理的事件数量,即每秒处理事件的频率
  • burst:令牌桶的最大数量,如果burst为0,并且limit == Inf,则允许处理任何事件,否则不允许
  • tokens:令牌桶中可用的令牌数量
  • last:记录上次limiter的tokens被更新的时间
  • lastEvent:lastEvent记录速率受限制(桶中没有令牌)的时间点,该时间点可能是过去的,也可能是将来的(Reservation预定的结束时间点)

Reservation结构体

type Reservation struct {
	ok        bool
	lim       *Limiter
	tokens    int
	timeToAct time.Time
	// This is the Limit at reservation time, it can change later.
	limit Limit
}

各个字段含义如下:

ok:到截至时间是否可以获取足够的令牌

lim:limiter对象

tokens:需要获取的令牌数量

timeToAct:需要等待的时间点

limit:代表预定的时间,是可以更改的。

reservation就是一个预定令牌的操作,timeToAct是本次预约需要等待到的指定时间点才有足够预约的令牌。

Limiter消费token

Limiter有三个token的消费方法,分别是Allow、Reserve和Wait,最终三种消费方式都调用了 reserveN 、advance这两个方法来生成和消费 Token。所以我们主要看看reserveN、advance函数的具体实现。

advance方法的实现:

func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
	//last不能在当前时间now之后,否则计算出来的elapsed为负数,会导致令牌桶数量减少
  last := lim.last
	if now.Before(last) {
		last = now
	}
	//根据令牌桶的缺数计算出令牌桶未进行更新的最大时间
	maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
	elapsed := now.Sub(last) //令牌桶未进行更新的时间段
	if elapsed > maxElapsed {
		elapsed = maxElapsed
	}
	//根据未更新的时间(未向桶中加入令牌的时间段)计算出产生的令牌数
	delta := lim.limit.tokensFromDuration(elapsed)
	tokens := lim.tokens + delta //计算出可用的令牌数
	if burst := float64(lim.burst); tokens > burst {
		tokens = burst
	}
	return now, last, tokens
}

advance方法的作用是更新令牌桶的状态,计算出令牌桶未更新的时间(elapsed),根据elapsed算出需要向桶中加入的令牌数delta,然后算出桶中可用的令牌数newTokens.

reserveN方法的实现:reserveN是 AllowN, ReserveN及 WaitN的辅助方法,用于判断在maxFutureReserve时间内是否有足够的令牌。

// @param n 要消费的token数量
// @param maxFutureReserve 愿意等待的最长时间
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
	lim.mu.Lock()
	// 如果没有限制
	if lim.limit == Inf {
		lim.mu.Unlock()
		return Reservation{
			ok:        true, //桶中有足够的令牌
			lim:       lim,
			tokens:    n,
			timeToAct: now,
		}
	}
	//更新令牌桶的状态,tokens为目前可用的令牌数量
	now, last, tokens := lim.advance(now)
  // 计算取完之后桶还能剩能下多少token
	tokens -= float64(n)
	var waitDuration time.Duration
  // 如果token < 0, 说明目前的token不够,需要等待一段时间
	if tokens < 0 {
		waitDuration = lim.limit.durationFromTokens(-tokens)
	}
	ok := n <= lim.burst && waitDuration <= maxFutureReserve
	r := Reservation{
		ok:    ok,
		lim:   lim,
		limit: lim.limit,
	}
  // timeToAct表示当桶中满足token数目等于n的时间
	if ok {
		r.tokens = n
		r.timeToAct = now.Add(waitDuration)
	}
  // 更新桶里面的token数目
	// 更新last时间
	// lastEvent
	if ok {
		lim.last = now
		lim.tokens = tokens
		lim.lastEvent = r.timeToAct
	} else {
		lim.last = last
	}
	lim.mu.Unlock()
	return r
}

上面的代码我已经进行了注释,这里在总结一下流程:

  • 首选判断是否拥有速率限制,没有速率限制也就是桶中一致拥有足够的令牌。
  • 计算从上次取 Token 的时间到当前时刻,期间一共新产生了多少 Token:我们只在取 Token 之前生成新的 Token,也就意味着每次取Token的间隔,实际上也是生成 Token 的间隔。我们可以利用 tokensFromDuration, 轻易的算出这段时间一共产生 Token 的数目。所以当前 Token 数目 = 新产生的 Token 数目 + 之前剩余的 Token 数目 - 要消费的 Token 数目。
  • 如果消费后剩余 Token 数目大于零,说明此时 Token 桶内仍不为空,此时 Token 充足,无需调用侧等待。 如果 Token 数目小于零,则需等待一段时间。那么这个时候,我们可以利用 durationFromTokens 将当前负值的 Token 数转化为需要等待的时间。
  • 将需要等待的时间等相关结果返回给调用方

其实整个过程就是利用了 Token 数可以和时间相互转化 的原理。而如果 Token 数为负,则需要等待相应时间即可。

上面提到了durationFromTokens、tokensFromDuration这两个方法,是关键,他们的实现如下:

func (limit Limit) durationFromTokens(tokens float64) time.Duration {
	seconds := tokens / float64(limit)
	return time.Nanosecond * time.Duration(1e9*seconds)
}
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
	// Split the integer and fractional parts ourself to minimize rounding errors.
	// See golang.org/issues/34861.
	sec := float64(d/time.Second) * float64(limit)
	nsec := float64(d%time.Second) * float64(limit)
	return sec + nsec/1e9
}
  • durationFromTokens:功能是计算出生成N 个新的 Token 一共需要多久。
  • tokensFromDuration:给定一段时长,这段时间一共可以生成多少个 Token。

细心的网友会发现tokensFromDuration方法既然是计算一段时间一共可以生成多少个 Token,为什么不直接进行相乘呢?其实Golang最初的版本就是采用d.Seconds() * float64(limit)直接相乘实现的,虽然看上去一点问题没有,但是这里是两个小数相乘,会带来精度损失,所以采用现在这种方法实现,分别求出秒的整数部分和小数部分,进行相乘后再相加,这样可以得到最精确的精度。

limiter归还Token

既然我们可以消费Token,那么对应也可以取消此次消费,将token归还,当调用 Cancel() 函数时,消费的 Token 数将会尽可能归还给 Token 桶。归还也并不是那么简单,接下我们我们看看归还token是如何实现的。

func (r *Reservation) CancelAt(now time.Time) {
	if !r.ok {
		return
	}
	r.lim.mu.Lock()
	defer r.lim.mu.Unlock()
  /*
  1.如果无需限流
	2. tokens为0 (需要获取的令牌数量为0)
	3. 已经过了截至时间
	以上三种情况无需处理取消操作
	*/
	if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(now) {
		return
	}
	//计算出需要还原的令牌数量
	//这里的r.lim.lastEvent可能是本次Reservation的结束时间,也可能是后来的Reservation的结束时间,所以要把本次结束时间点(r.timeToAct)之后产生的令牌数减去
	restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))
  // 当小于0,表示已经都预支完了,不能归还了
	if restoreTokens &lt;= 0 {
		return
	}
	//从新计算令牌桶的状态
	now, _, tokens := r.lim.advance(now)
	//还原当前令牌桶的令牌数量,当前的令牌数tokens加上需要还原的令牌数restoreTokens
	tokens += restoreTokens
  //如果tokens大于桶的最大容量,则将tokens置为桶的最大容量
	if burst := float64(r.lim.burst); tokens &gt; burst {
		tokens = burst
	}
	// update state
	r.lim.last = now //记录桶的更新时间
	r.lim.tokens = tokens //更新令牌数量
 // 如果都相等,说明跟没消费一样。直接还原成上次的状态吧
	if r.timeToAct == r.lim.lastEvent {
		prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))
		if !prevEvent.Before(now) {
			r.lim.lastEvent = prevEvent
		}
	}
	return
}

注释已经添加,就不在详细解释了,重点是这一行代码:

restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct)),

  • r.tokens指的是本次消费的token数,
  • r.timeToAcr指的是Token桶可以满足本次消费数目的时刻,也就是消费的时刻+等待的时长
  • r.lim.lastEvent指的是最近一次消费的timeToAct的值,

通过r.limit.tokensFromDuration方法得出的结果指的是从该次消费到当前时间,一共又消费了多少Token数目,所以最终得出这一段的代码含义是:

要归还的Token = 该次消费的Token - 新消费的token。

好啦,源码就暂时分析到这了,因为标准库的实现的代码量有点大,还有一部分在这里没有说,留给大家自己去剖析吧~。

总结

本文重点介绍了漏桶算法和令牌桶算法,漏桶算法和令牌桶算法的主要区别在于,"漏桶算法"能够强行限制数据的传输速率(或请求频率),而"令牌桶算法"在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在某些情况下,漏桶算法不能够有效地使用网络资源,因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。通常,漏桶算法与令牌桶算法结合起来为网络流量提供更高效的控制。

文中测试代码已上传:

https://github.com/asong2020/Golang_Dream/tree/master/code_demo/limit_demo

更多关于golang高并发限流漏桶令牌桶的资料请关注我们其它相关文章!

(0)

相关推荐

  • golang高并发限流操作 ping / telnet

    需求 当需要同时ping/telnet多个ip时,可以通过引入ping包/telnet包实现,也可以通过go调用cmd命令实现,不过后者调用效率较差,所以这里选择ping包和telnet包 还有就是高并发的问题,可以通过shell脚本或者go实现高并发,所以我选择的用go自带的协程实现,但是如果要同时处理1000+个ip,考虑到机器的性能,需要ratelimit控制开辟的go协程数量,这里主要写一下我的建议和淌过的坑 ping 参考链接: https://github.com/sparrc/go

  • 基于Golang 高并发问题的解决方案

    Golang 高并发问题的解决 Golang在高并发问题上,由于协程的使用,相对于其他编程语言,已经有了很大的优势,即相同的配置上,Golang可以以更低的代价处理更多的线程,同样的线程数,占用更低的资源!及时这样,只是解决了一部分问题而已,因为在每个协程里,处理逻辑还是会有问题. 高并发时,还是要考虑服务器所能承受的最大压力,数据库读取时的io问题,连接数问题,带宽问题等等 研究了一下并发解决方案,在此记录一下 参考文章:Handling 1 Million Requests per Minu

  • 详解Golang实现请求限流的几种办法

    简单的并发控制 利用 channel 的缓冲设定,我们就可以来实现并发的限制.我们只要在执行并发的同时,往一个带有缓冲的 channel 里写入点东西(随便写啥,内容不重要).让并发的 goroutine在执行完成后把这个 channel 里的东西给读走.这样整个并发的数量就讲控制在这个 channel的缓冲区大小上. 比如我们可以用一个 bool 类型的带缓冲 channel 作为并发限制的计数器. chLimit := make(chan bool, 1) 然后在并发执行的地方,每创建一个新

  • Golang模拟令牌桶进行对访问的限流方式

    利用channel进行模拟令牌桶对访问进行限流 func FW(max int,duration time.Duration){ //定义一个channel ,进行初始化 contain := make(chan bool , max) for i := 0 ; i < max ; i ++{ contain <- true//写入channel } go func() {//开启一个线程 for { contain <- true time.Sleep(duration) } }()

  • 关于golang高并发的实现与注意事项说明

    一.并发的意义 并发的意义就是让 一个程序同时做多件事情,其目的只是为了能让程序同时做另一件事情而已,而不是为了让程序运行的更快(如果是多核处理器,而且任务可以分成相互独立的部分,那么并发确实可以让事情解决的更快). golang从语言级别上对并发提供了支持,而且在启动并发的方式上直接添加了语言级的关键字,不必非要按照固定的格式来定义线程函数,也不必因为启动线程的时候只能给线程函数传递一个参数而烦恼. 二.并发的启动 go的并发启动非常简单,几乎没有什么额外的准备工作,要并发的函数和一般的函数没

  • golang高并发系统限流策略漏桶和令牌桶算法源码剖析

    目录 前言 漏桶算法 样例 源码实现 令牌桶算法 样例 源码剖析 Limit类型 Limiter结构体 Reservation结构体 Limiter消费token limiter归还Token 总结 前言 今天与大家聊一聊高并发系统中的限流技术,限流又称为流量控制,是指限制到达系统的并发请求数,当达到限制条件则可以拒绝请求,可以起到保护下游服务,防止服务过载等作用.常用的限流策略有漏桶算法.令牌桶算法.滑动窗口:下文主要与大家一起分析一下漏桶算法和令牌桶算法,滑动窗口就不在这里这介绍了.好啦,废

  • Java高并发系统限流算法的实现

    目录 1 概述 2 计数器限流 2.1 概述 2.2 实现 2.3 结果分析 2.4 优缺点 2.5 应用 3 漏桶算法 3.1 概述 3.2 实现 3.3 结果分析 3.4 优缺点 4 令牌桶算法 4.1 概述 4.2 实现 4.3 结果分析 4.4 应用 5 滑动窗口 5.1 概述 5.2 实现 5.3 结果分析 5.4 应用 1 概述 在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.限流可以认为服务降级的一种,限流是对系统的一种保护措施.即限制流量请求的频率(每秒处理多少个请求

  • php使用lua+redis实现限流,计数器模式,令牌桶模式

    lua 优点 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输; 原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务; 复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用. 计数器模式: 利用lua脚本一次性完成处理达到原子性,通过INCR自增计数,判断是否达到限定值,达到限定值则返回限流,添加key过期时间应该范围过度 $lua = ' local i = redis.call("INCR&

  • redis实现的四种常见限流策略

    目录 引言 固定时间窗口算法 实现 滑动时间窗口算法 实现 漏桶算法 实现 令牌桶算法 引言 在web开发中功能是基石,除了功能以外运维和防护就是重头菜了.因为在网站运行期间可能会因为突然的访问量导致业务异常.也有可能遭受别人恶意攻击 所以我们的接口需要对流量进行限制.俗称的QPS也是对流量的一种描述 针对限流现在大多应该是令牌桶算法,因为它能保证更多的吞吐量.除了令牌桶算法还有他的前身漏桶算法和简单的计数算法 下面我们来看看这四种算法 固定时间窗口算法 固定时间窗口算法也可以叫做简单计数算法.

  • 详解基于redis实现的四种常见的限流策略

    目录 一.引言 二.固定时间窗口算法 三.滑动时间窗口算法 四.漏桶算法 五.令牌桶算法 一.引言 在web开发中功能是基石,除了功能以外运维和防护就是重头菜了.因为在网站运行期间可能会因为突然的访问量导致业务异常.也有可能遭受别人恶意攻击 所以我们的接口需要对流量进行限制.俗称的QPS也是对流量的一种描述 针对限流现在大多应该是令牌桶算法,因为它能保证更多的吞吐量.除了令牌桶算法还有他的前身漏桶算法和简单的计数算法 下面我们来看看这四种算法 二.固定时间窗口算法 固定时间窗口算法也可以叫做简单

  • 高并发系统的限流详解及实现

    在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.本文结合作者的一些经验介绍限流的相关概念.算法和常规的实现方式. 缓存 缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪.使用缓存不单单能够提升系统访问速度.提高并发访问量,也是保护数据库.保护系统的有效方式.大型网站一般主要是"读",缓存的使用很容易被想到.在大型"写"系统中,缓存也常常扮演者非常重要的角色.比如累积一些数据批量写入,内存里面的缓存队列(生产消费),以及

  • Python+redis通过限流保护高并发系统

    保护高并发系统的三大利器:缓存.降级和限流.那什么是限流呢?用我没读过太多书的话来讲,限流就是限制流量.我们都知道服务器的处理能力是有上限的,如果超过了上限继续放任请求进来的话,可能会发生不可控的后果.而通过限流,在请求数量超出阈值的时候就排队等待甚至拒绝服务,就可以使系统在扛不住过高并发的情况下做到有损服务而不是不服务. 举个例子,如各地都出现口罩紧缺的情况,广州政府为了缓解市民买不到口罩的状况,上线了预约服务,只有预约到的市民才能到指定的药店购买少量口罩.这就是生活中限流的情况,说这个也是希

  • Golang实现常见的限流算法的示例代码

    目录 固定窗口 滑动窗口 漏桶算法 令牌桶 滑动日志 总结 限流是项目中经常需要使用到的一种工具,一般用于限制用户的请求的频率,也可以避免瞬间流量过大导致系统崩溃,或者稳定消息处理速率 这个文章主要是使用Go实现常见的限流算法,代码参考了文章面试官:来,年轻人!请手撸5种常见限流算法! 和面试必备:4种经典限流算法讲解如果需要Java实现或更详细的算法介绍可以看这两篇文章 固定窗口 每开启一个新的窗口,在窗口时间大小内,可以通过窗口请求上限个请求. 该算法主要是会存在临界问题,如果流量都集中在两

  • java分布式面试系统限流最佳实践

    目录 引言 1.面试官: 哪些场景系统使用了限流?为什么要使用限流? 2.面试官: 那你了解哪些常用限流算法? 1.计数器方法: 2.漏斗算法: 3.令牌桶算法: 3.面试官: 那具体这值该如何评估,说到现在我还是不知道限流到底要怎么设置,可以给我一点经验方法吗? 深入分析 使用线程池实现: 借助Guava实现: 总结 引言 前面讲了系统中的降级熔断设计和对 Hystrix 组件的功能了解,关于限流降级还有一个比较重要的知识点就是限流算法. 如果你面试的是电商相关公司,这一块就显得更加重要了,秒

随机推荐