Skip to content

限流

背景

  1. 服务器扛不住了
  2. 资源稀缺,或者处于安全防范的目的采用的自我保护机制,保证有限的资源提供最大的服务能力(缓存,降级,限流都可实现)

Semaphore 通常用于那些资源有明确访问数量限制的场景比如限流(仅限于单机模式,实际项目中推荐使用 Redis +Lua 来做限流)。

问题?redis + lua 怎么写

虽然Redis 官方没有直接提供限流相应的API,但却支持了 Lua 脚本的功能,可以使用它实现复杂的令牌桶或漏桶算法,也是分布式系统中实现限流的主要方式之一。

并且通常我们使用Redis事务时,并不是直接使用Redis自身提供的事务功能,而是使用Lua脚本。 相比Redis事务,Lua脚本的优点:

  1. 减少网络开销:使用Lua脚本,无需向Redis 发送多次请求,执行一次即可,减少网络传输
  2. 原子操作:Redis 将整个Lua脚本作为一个命令执行,原子,无需担心并发
  3. 复用:Lua脚本一旦执行,会永久保存 Redis 中,其他客户端可复用 源码:https://gitee.com/it-wenbei/redis-limiter

按照力度划分

单机 + 分布式 含义:通常指的是在分布式节点下的某个服务的限流,各服务节点采用不同的限流保护措施

分布式限流:在接入层实现多节点的合并限流,比如利用nginx和redis,分布式网关等 分布式限流需要中心化存储,常见使用redis实现,引入了中心化存储,需要解决一些问题

  1. 数据一致性:理论存在的任意时间,任意组件的数据都是完全一致得,根据cap也不可以实现,只是达到线性一致(理想的模式)
  2. 时间一致性:各个服务器的时间要达到一致性,如果服务器时间有异常,会对时间窗口敏感的算法造成误差
  3. 超时:由于网络抖动,或者redis(分布式限流的中间件压力过大响应变慢),超时阈值设置不合理,是否需要放行流量还是拒绝流量
  4. 性能及可靠性:redis等分布式限流中间件的资源有限,如果是单点限流服务,出现性能上限,分布式限流如何退换成单机限流模式

按照限流算法的分类

限流无论是按照什么维度,底层实现一定是相应的算法

固定窗口计数器

总结: 计算器超过了限流的阈值,停止服务,等带下一个窗口,计数为0重新提供服务 问题:

  1. 没有应对突发的流量的能力:如100的qps,前100ms来了99个,后900ms只能处理一个,长时间处理不了
  2. 在一个窗口内后半段时间来了100个请求,以及在后一个窗口的前半段时间100个请求,这两个汇聚在一起相当于一个窗口处理了200个请求

计数器(滑动窗口计数器)

  1. 将单位时间划分为多个区间,一般均分为多个小时间段
  2. 每一个区间都有一个计数器,有一个请求则该区间的请求就会就+1
  3. 每过一段时间,时间窗口向前移动,,抛弃最老的区间,纳入新的一个区间
  4. 计算阈值的时候,需要判断,整个滑动窗口所有个区间,的数量/时间,超过阈值,进行限流 好处 : 解决了固定窗口的不稳定及突发情况, 坏处: 如果区间划分很细很小时间段很小,需要较大的内存空间进行数据统计 实现: 使用redis的zset和循环队列实现, 将key作为标志id, value作为uuid唯一值, score作为时间戳,利用zdd,expire,zcount,zremember实现,启用pipeline提高性能重点!!需要代码实现,整理出链接

漏筒算法

漏的水滴:固定速率,服务的处理速度 实现步骤:

  1. 将每个请求放入固定的大小的队列进行存储
  2. 以固定的速率向外流出请求,队列为空则停止流出
  3. 队列满了则拒绝服务

缺点: :短时间内有大量的突发请求,即使此时服务器的负载不高,每个请求也都得在队列中等待一段时间才能得到处理

Redis 4.0 提供了一个限流 Redis 模块,它叫 redis-cell。

该模块只有 1 条指令 cl.throttle, cl.throttle zhangfan:reply 15 30 60

  1. 15初始漏洞容量
  2. 30/60 频率。60s最多30次请求

令牌桶

总结:以一个恒定的速率向桶中加令牌,而如果请求需要被处理,则先从桶中获取一个令牌,没有令牌则拒绝服务,(与漏桶区别,一个是请求进桶,一个是那令牌出桶) 解决了漏筒的突发场景:令牌桶支持突发流量:因为桶中有多少令牌在等待,就允许有多少突发请求可以执行 实现步骤:

  1. 以恒定的速率向桶中增加令牌
  2. 如果令牌满了直接丢弃,如果有请求则获取令牌进行请求
  3. 如果桶空了,则拒绝执行

四种策略模式的选择:

  1. 固定窗口:解决燃煤之急
  2. 滑动窗口:实现简单,少量的突发情况
  3. 漏筒算法:对于流量的均匀有很强的的要求
  4. 令牌:突发性请求较多

限流设计名言: 在具有复杂依赖关系的系统中,对特定服务的进行过载控制可能对整个系统有害或者服务的实现有缺陷

做限流阈值需要注意的场景:

  1. 系统运行指标:cpu,内存,线程数,网络连接数
  2. 资源之间的调用关系,服务之间的强弱依赖
  3. 控制方法,对限流后的请求,采用何种方式(直接拒绝,快速失败or 排队等待等)

Netfix家的Hystrix

  1. 当 Hystrix Command 请求后端服务失败数量超过一定比例(默认 50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务
  2. 断路器保持在开路状态一段时间后(默认 5 秒), 自动切换到半开路状态(HALF-OPEN).
  3. 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN)

java 实现的限流类库

  1. RateLimiter
    1. 单机场景下,zuul的组件
  2. Spring Cloud Gateway
    1. 使用 Redis 和 Lua 脚本实现了令牌桶的方式
    2. redis-rate-limiter.replenishRate :允许用户每秒处理的请求数。设置的数值就代表 每秒向令牌桶添加多少个令牌 。 redis-rate-limiter.burstCapacity :令牌桶的容量,即允许在 1 秒内完成的最大请求数。设置为 0 则表示拒绝所有请求。
  3. Sentinel
    1. 控制台:基于SpringBoot开发,打包后可直接运行,不需要部署Tomcat应用容器
    2. 核心库:不依赖任何框架,能够运行于所有Java运行时环境
    3. 底层是滑动窗口的实现

基于 VitePress 构建