降级,熔断,限流的区别:


先导篇:服务降级、熔断、限流的区别


以设计一个整点限量秒杀系统为例,包括登录、抢购、支付等核心功能

  • 降级(丢车保帅):在秒杀时,通过服务降级把注册、修改个人信息等非核心功能关闭掉。
  • 熔断:支付依赖第三方服务,要设置熔断策略,熔断后要给出友好提示,比如10分钟后再来支付。
  • 限流:抢购下单接口采用限流方式,如抢购1000件商品,则设置2000大小的队列,请求超过2000后直接拒绝掉


几种常用的限流算法


限流又称为流量控制,是指限制到达系统的并发请求数,当达到限制条件则可以拒绝请求,可以起到保护下游服务,防止服务过载等作用。


此处的,可以视为是一个抽象的概念;可以是CDN或音视频传输的字节流流量,可以是接口请求的QPS,可以是业务流频度,甚至可以是数据读写频度。

而限流算法就是对服务 按照预设的规则,进行流量限制或功能限制的一种方法。


通俗易懂 限流算法原理剖析(京东)

字节QA的这篇常用的限流算法,一大半内容和上文雷同..

https://xie.infoq.cn/article/bf4eeabeecf0d87b399c82e4e

http://dockone.io/article/9746

https://juejin.cn/post/7002027704912445448


Gin 限流中间件

高并发系统的限流策略:漏桶和令牌桶(附源码剖析)

Go生态比较流行的基于漏桶限流算法实现的限流库uber-go/ratelimit

Go生态比较流行的基于令牌桶限流算法实现的限流库 juju/ratelimit,Go官方也提供了相关功能,在golang.org/x/time/rate

Golang限流器time/rate使用介绍

Golang限流器time/rate实现剖析



计数器算法(即 固定窗口算法)


分成多个时间片段,每个时间片段分配一定数量的请求额度。

每个时间片段相对隔离,一个时间片段区间的请求量统计跟其他区间的请求量统计完全独立。当一个时间片段过期,自动进入下一个时间片段重新进行计数,逻辑图如下:


弊端:


算法相对简单,但会存在临界问题

临界问题 :用户流量并不会像我们所期望的那样匀速请求,而可能在某个时间点集中爆发。

如上图所示:每个时间片段为1s。 在第一个时间单位内的前800ms只有一次请求,后200ms(800ms-1s范围内)内承载999次请求,而第二个时间单位内前200ms承载1001-2000次共1000个请求。这样系统在400ms(第800ms到1200ms时间范围内)内共承载了1999次用户请求,此访问量已经达到系统所能承载的最大QPS 1000次请求的两倍。


滑动窗口算法


滑动窗口算法的时间单位不再是彼此独立,而是步步递进,彼此重叠。

滑动窗口算法将固定窗口算法的时间单位 进一步细化,如将1秒再分为10个时间网格,每个网格占用100ms的时间。第一个时间单位为0ms-1000ms、第二个时间单位为100ms-1100ms、第三个时间单位为200ms-1200ms,以此类推。每个时间单位的请求总量都需小于设定的最大请求量


弊端:


由于滑动窗口算法每次都需要统计单位时间的请求量,开销远大于固定窗口算法,所以在真实的业务环境中需要慎重使用滑动窗口算法。


漏桶算法


漏桶算法以绝对平均的速度处理用户请求,无论用户请求有多少,最终消费/处理 用户请求的速率是固定不变的。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"go.uber.org/ratelimit"
"time"
)

// 定义全局限流器对象
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

其中-n表示请求数,-c表示并发数

上一个请求处理的时间是at

每秒请求量(RPS)

弊端


在真实的业务场景中,漏桶算法面对合法的突发流量,有点捉襟见肘。


令牌桶算法


这两种方法(漏桶和令牌桶)看起来很像,不过还是有区别的。漏桶流出的速率固定,而令牌桶只要在桶中有令牌,那就可以拿。也就是说令牌桶是允许一定程度的并发的,比如同一个时刻,有100个用户请求,只要令牌桶中有100个令牌,那么这100个请求全都会放过去。令牌桶在桶中没有令牌的情况下也会退化为漏桶模型,实际应用中令牌桶应用较为广泛。


对比


来自 常用限流算法


来自 限流算法实践(360云计算平台)


来自 图解+代码|常见限流算法以及限流在单机分布式场景下的思考

来自 一文搞懂高频面试题之限流算法,从算法原理到实现,再到对比分析




Go 中基于 IP 地址的 HTTP 请求限流

nginx的限流策略以及google的guava限流策略。

市面上一些比较成熟的限流工具和框架:

  • Google的Guava中基于令牌桶实现的限流组件,拿来即用;Guava 是一个 Google 开源项目,包含了若干被 Google 的 Java 项目广泛依赖的核心库,其中的 RateLimiter 提供了令牌桶算法实现:平滑突发限流 (SmoothBursty) 和平滑预热限流 (SmoothWarmingUp) 实现。

  • Alibaba开源的面向分布式服务架构的流量控制框架Sentinel,基于滑动窗口实现

  • Nginx: 对于 Nginx 接入层限流可以使用 Nginx 自带了两个模块:1. 连接数限流模块 ngx_http_limit_conn_module; 2. 漏桶算法实现的请求限流模块 ngx_http_limit_req_module

高并发场景下限流,常见的限流算法、方案解析!