常用的限流策略简介——以漏桶和令牌桶为例

常用的限流策略简介——以漏桶和令牌桶为例

这部分内容参考并搬运自 q1mi 老师的技术博客,原文的链接为:https://liwenzhou.com/posts/Go/ratelimit/

限流

限流又称流量控制(流控),通常是指限制到达系统的并发请求数。

互联网上需要限流的业务场景非常多,比如电商系统的秒杀、微博上突发热点新闻、双十一购物节、12306抢票等等。这些场景下的用户请求量通常会激增,远远超过平时正常的请求量,此时如果不加任何限制很容易就会将后端服务打垮,影响服务的稳定性(限制一部分用户的请求总比所有所有用户的服务都宕掉要好,至少保证了一部分用户的体验)。

常用的限流策略

漏桶

漏桶法限流很好理解,假定我们有一个水桶,按照固定的速率向下方滴水,无论有多少请求,无论请求的速率有多大,都按照固定的速率流出,对应到系统当中就是按照固定的速率处理请求。【感觉非常像百度网盘对非会员的限流策略,不论请求有多少、请求的速率有多大,都按照固定的速率流出】请添加图片描述
漏桶法的关键点在于漏桶始终按照固定的速率运行,但是它并不能很好的处理有大量突发请求的场景,毕竟在某些场景下我们可能需要提高系统的处理效率,而不是一味的按照固定速率处理请求

令牌桶

令牌桶与漏桶的原理类似。令牌桶按照固定的速率将令牌放入桶内,并且只要能从桶里取出令牌,就可以进一步对请求进行处理,令牌桶支持突发流量的快速处理。请添加图片描述
如果从桶中取不出令牌,那么可以选择等待,也可以直接拒绝请求并返回错误码。

在 GIN 框架中使用限流中间件

现在我们使用令牌桶作为限流策略,编写一个限流中间件,在 GIN 框架当中使用它,并应用到 bluebell 项目上:

限流策略使用的是令牌桶,我们没有重新造轮子,而是使用现成的由其它开发者开发的软件包,使用下述命令安装:

go get -u github.com/juju/ratelimit

之后,我们开始编写限流中间件。GIN 的中间件是一个函数,它以 *gin.Context 为参数:

func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *gin.Context) {
	bucket := ratelimit.NewBucket(fillInterval, cap)
	return func(c *gin.Context) {
		// 如果取不到令牌就返回响应
		if bucket.TakeAvailable(1) == 0 {
			c.String(http.StatusOK, "rate limit...")
			c.Abort()
			return
		}
		c.Next()
	}
}

我们来看一下ratelimit.NewBucket()的文档:

NewBucket returns a new token bucket that fills at the rate of one
token every fillInterval, up to the given maximum capacity. Both
arguments must be positive. The bucket is initially full.

翻译过来就是,NewBucket 返回一个新的令牌桶对象,它会在每一个 fillInterval 时间间隔(它是一个time.Duration类型)向桶中填充一个 token,直到达到令牌桶的上限(即第二个参数 cap)。这两个参数都必须是正的值。在开始时,令牌桶为空。

现在我们在 GIN 框架下使用限流中间件:

r.Use(logger.GinLogger(), logger.GinRecovery(true), middleware.RateLimitMiddleware(2*time.Second, 1))

r.Use的参数是HandlerFunc...,因此它理论上支持无限拓展中间件。我们已经使用了 GinLogger 和 GinRecovery 两个中间件,它们的作用是使用 zap 作为默认的日志库对 web 服务进行日志记录,在此基础上,我们为全局添加一个限流中间件,并设置好参数。

此时,我们随意在浏览器访问一个接口,重复访问将会得到"rate limit..."的提示:
在这里插入图片描述
至此,我们根据 q1mi 老师的技术博客,了解了限流的概念以及在 web 服务中限流的必要性。我们了解了漏桶和令牌桶两种常见的限流策略,令牌桶可以处理突发流量的场景,这一点比漏桶机制更优。我们进一步在 GIN 框架下为全局添加了一个限流中间件,并在 bluebell 项目中对 api 进行了限流测试。

### 令牌桶算法与算法对比 #### 实现原理 令牌桶算法(Token Bucket Algorithm)通过维持一个固定容量的令牌桶来控制数据包的发送速率。每当有新请求到来时,会尝试消耗一定数量的令牌;如果此时有足够的令牌,则允许该操作继续执行并减少相应数量的令牌。反之则拒绝服务或延迟处理直至获得足够的资源许可[^3]。 相比之下,算法(Leaky Bucket Algorithm)采用了一种更为严格的流量调控机制——想象有一个带有孔洞的容器持续向外渗水,当新的水流进入此装置内部时会被暂时储存起来等待缓慢流出。这意味着即使短时间内有大量的输入到达也只会按照固定的滴速排出,从而实现了平滑的数据流输出效果[^1]。 #### 主要差异 最显著的不同之处在于两者对于突发流量的支持程度: - **突发支持**:由于其设计特性,“令牌桶”可以容纳一定程度上的瞬时高峰负荷,在满足预设条件的情况下允许一次性释放较多量级的信息传递出去; - **严格限速**:“”的工作方式决定了它不具备这样的灵活性,始终保持着恒定不变的速度对外提供服务而不受外界因素干扰[^2]。 #### 应用场景 基于上述特点,两种方法适用于不同类型的业务需求: - 对于那些可能经历间歇性高并发访问压力的服务端接口而言,比如社交平台的消息推送功能或是电商平台促销活动期间的商品详情页加载过程,选用“令牌桶策略往往更加合适因为这能兼顾性能优化的同时保障用户体验不受太大影响; - 而在网络通信领域内涉及到实时性强且对延时敏感的任务时(如VoIP通话),为了确保音视频质量稳定可靠,则更倾向于依赖“”来进行精准的时间间隔调整以防止抖动现象发生。 ```python import time from threading import Thread, Lock class TokenBucket: def __init__(self, rate_limit, bucket_size): self.rate_limit = rate_limit # 每秒产生的token数目 self.bucket_size = bucket_size # token的最大容量 self.tokens = min(bucket_size, rate_limit * (time.time() % 1)) self.lock = Lock() def consume(self, tokens_needed=1): with self.lock: now = int(time.time()) if (now > self.last_time and self.tokens < self.bucket_size): elapsed = now - self.last_time new_tokens = elapsed * self.rate_limit self.tokens = min(self.bucket_size, self.tokens + new_tokens) self.last_time = now if self.tokens >= tokens_needed: self.tokens -= tokens_needed return True return False def leaky_bucket(data_rate, max_queue_length): queue = [] while True: data_incoming = yield from get_data() if len(queue) < max_queue_length: queue.append(data_incoming) processed_item = None if queue: processed_item = queue.pop(0) send_to_next_hop(processed_item) time.sleep(1 / data_rate) # Note: The above code snippets are simplified examples to illustrate the concepts. ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值