El nuevo director técnico llama elegancia a la realización de la limitación de corriente, ¡lo admiro!

Hola a todos, soy Louie!

En el escenario de alta concurrencia del comercio electrónico, a menudo usamos algunos métodos comunes para lidiar con los picos de tráfico, como la limitación actual, la fusión y la degradación.Hoy hablaremos sobre la limitación actual.

¿Qué es la limitación de corriente? La limitación actual es limitar la cantidad de solicitudes simultáneas que llegan al sistema para garantizar que el sistema pueda responder normalmente a algunas solicitudes de los usuarios. Para el tráfico que excede el límite, la disponibilidad general del sistema está garantizada por denegación de servicio.

Según el alcance de la limitación de corriente, se puede dividir en limitación de corriente independiente y limitación de corriente distribuida .

Métodos comunes de limitación de corriente

encimera

El contador es el algoritmo de limitación de corriente más simple. Su principio es: dentro de un cierto período de tiempo, se cuenta la solicitud y se compara el umbral para determinar si se requiere limitación de corriente. Una vez que se alcanza el punto crítico de tiempo, el contador se borra .

Esto es como cuando va al automóvil. Cuántas posiciones se especifican en el vagón, y no se le permitirá abordar el automóvil cuando esté lleno. De lo contrario, estará sobrecargado. Si lo atrapa la policía de tránsito tio, te van a multar. Si se usa nuestro sistema, no es cuestion de multas. , puede colapsar directamente.

Lógica de ejecución del programa:

  • Puede establecer un recuento variable en el programa. Cuando llegue una solicitud, agregaré 1 a este número y registraré el tiempo de la solicitud al mismo tiempo.
  • Cuando llegue la próxima solicitud, juzgue si el valor de conteo excede la frecuencia establecida y si el tiempo de la solicitud actual y el tiempo de la primera solicitud están dentro de 1 minuto.
  • Si está dentro de 1 minuto y excede la frecuencia establecida, prueba que hay demasiadas solicitudes y las solicitudes posteriores serán rechazadas.
  • Si el intervalo entre la solicitud y la primera solicitud es mayor que el período de conteo, y el valor de conteo todavía está dentro del rango límite actual, restablezca el conteo.

Entonces viene el problema, si hay un requisito para permitir un máximo de 200 accesos por minuto para una interfaz/consulta, supongamos que un usuario envía 200 solicitudes en los últimos milisegundos del segundo 59 y el contador se borra después del segundo 59. ., envía otras 200 solicitudes en el siguiente segundo.

那么在 1 秒钟内这个用户发送了 2 倍的请求,这个是符合我们的设计逻辑的,这也是计数器方法的设计缺陷,系统可能会承受恶意用户的大量请求,甚至击穿系统。这种方法虽然简单,但也有个大问题就是没有很好的处理单位时间的边界。

不过说实话,这个计数引用了锁,在高并发场景,这个方式可能不太实用,我建议将锁去掉,然后将 l.count++ 的逻辑通过原子计数处理,这样就可以保证 l.count 自增时不会被多个线程同时执行,即通过原子计数的方式实现限流。

为了不影响阅读,代码详见:github.com/lml20070115…

滑动窗口

滑动窗口是针对计数器存在的临界点缺陷,所谓滑动窗口(Sliding window)是一种流量控制技术,这个词出现在 TCP 协议中。滑动窗口把固定时间片进行划分,并且随着时间的流逝,进行移动,固定数量的可以移动的格子,进行计数并判断阀值。

上图中我们用红色的虚线代表一个时间窗口(一分钟),每个时间窗口有 6 个格子,每个格子是 10 秒钟。每过 10 秒钟时间窗口向右移动一格,可以看红色箭头的方向。我们为每个格子都设置一个独立的计数器 Counter,假如一个请求在 0:45 访问了那么我们将第五个格子的计数器 +1(也是就是 0:40~0:50),在判断限流的时候需要把所有格子的计数加起来和设定的频次进行比较即可。

那么滑动窗口如何解决我们上面遇到的问题呢?来看下面的图:

当用户在 0:59 秒钟发送了 200 个请求就会被第六个格子的计数器记录 +200,当下一秒的时候时间窗口向右移动了一个,此时计数器已经记录了该用户发送的 200 个请求,所以再发送的话就会触发限流,则拒绝新的请求。

其实计数器就是滑动窗口啊,只不过只有一个格子而已,所以想让限流做的更精确只需要划分更多的格子就可以了,为了更精确我们也不知道到底该设置多少个格子,格子的数量影响着滑动窗口算法的精度,依然有时间片的概念,无法根本解决临界点问题。

为了不影响阅读,代码详见:github.com/RussellLuo/…

漏桶

漏桶算法(Leaky Bucket),原理就是一个固定容量的漏桶,按照固定速率流出水滴。

用过水龙头都知道,打开龙头开关水就会流下滴到水桶里,而漏桶指的是水桶下面有个漏洞可以出水,如果水龙头开的特别大那么水流速就会过大,这样就可能导致水桶的水满了然后溢出。

图片如果看不清,可单击图片并放大。

一个固定容量的桶,有水流进来,也有水流出去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率(处理速度),从而达到流量整形和流量控制的效果。

漏桶算法有以下特点:

  • 漏桶具有固定容量,出水速率是固定常量(流出请求)
  • 如果桶是空的,则不需流出水滴
  • 可以以任意速率流入水滴到漏桶(流入请求)
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出(新请求被拒绝)

漏桶限制的是常量流出速率(即流出速率是一个固定常量值),所以最大的速率就是出水的速率,不能出现突发流量。

为了不影响阅读,代码详见:github.com/lml20070115…

令牌桶

令牌桶算法(Token Bucket)是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。

图片如果看不清,可单击图片并放大。

我们有一个固定的桶,桶里存放着令牌(token)。一开始桶是空的,系统按固定的时间(rate)往桶里添加令牌,直到桶里的令牌数满,多余的请求会被丢弃。当请求来的时候,从桶里移除一个令牌,如果桶是空的则拒绝请求或者阻塞。

令牌桶有以下特点:

  • 令牌按固定的速率被放入令牌桶中
  • 桶中最多存放 B 个令牌,当桶满时,新添加的令牌被丢弃或拒绝
  • 如果桶中的令牌不足 N 个,则不会删除令牌,且请求将被限流(丢弃或阻塞等待)

令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌...),并允许一定程度突发流量,所以也是非常常用的限流算法。

为了不影响阅读,代码详见:github.com/lml20070115…

Redis + Lua 分布式限流

单机版限流仅能保护自身节点,但无法保护应用依赖的各种服务,并且在进行节点扩容、缩容时也无法准确控制整个服务的请求限制。

而分布式限流,以集群为维度,可以方便的控制这个集群的请求限制,从而保护下游依赖的各种服务资源。

分布式限流最关键的是要将限流服务做成原子化,我们可以借助 Redis 的计数器,Lua 执行的原子性,进行分布式限流,大致的 Lua 脚本代码如下:

local key = "rate.limit:" .. KEYS[1--限流KEY
local limit = tonumber(ARGV[1])        --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
  return 0
else  --请求数+1,并设置1秒过期
  redis.call("INCRBY", key,"1")
   redis.call("expire", key,"1")
   return current + 1
end
复制代码

限流逻辑(Java 语言):

public static boolean accquire() throws IOException, URISyntaxException {
    Jedis jedis = new Jedis("127.0.0.1");
    File luaFile = new File(RedisLimitRateWithLUA.class.getResource("/").toURI().getPath() + "limit.lua");
    String luaScript = FileUtils.readFileToString(luaFile);

    String key = "ip:" + System.currentTimeMillis()/1000; // 当前秒
    String limit = "5"; // 最大限制
    List<String> keys = new ArrayList<String>();
    keys.add(key);
    List<String> args = new ArrayList<String>();
    args.add(limit);
    Long result = (Long)(jedis.eval(luaScript, keys, args)); // 执行lua脚本,传入参数
    return result == 1;
}
复制代码

聊聊其它

上面的限流方式,主要是针对服务器进行限流,我们也可以对容器进行限流,比如 Tomcat、Nginx 等限流手段。

Tomcat 可以设置最大线程数(maxThreads),当并发超过最大线程数会排队等待执行;而 Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数。

对于 Java 语言,我们其实有相关的限流组件,比如大家常用的 RateLimiter,其实就是基于令牌桶算法,大家知道为什么唯独选用令牌桶么?

对于 Go 语言,也有该语言特定的限流方式,比如可以通过 channel 实现并发控制限流,也支持第三方库 httpserver 实现限流,详见这篇 《Go 限流的常见方法》

在实际的限流场景中,我们也可以控制单个 IP、城市、渠道、设备 id、用户 id 等在一定时间内发送的请求数;如果是开放平台,需要为每个 appkey 设置独立的访问速率规则。

限流对比

下面我们就对常用的线程策略,总结它们的优缺点,便于以后选型。

计数器:

  • 优点:固定时间段计数,实现简单,适用不太精准的场景;
  • 缺点:对边界没有很好处理,导致限流不能精准控制。

滑动窗口:

  • 优点:将固定时间段分块,时间比“计数器”复杂,适用于稍微精准的场景;
  • 缺点:实现稍微复杂,还是不能彻底解决“计数器”存在的边界问题。

漏桶:

  • 优点:可以很好的控制消费频率;
  • 缺点:实现稍微复杂,单位时间内,不能多消费,感觉不太灵活。

令牌桶:

  • 优点:可以解决“漏桶”不能灵活消费的问题,又能避免过渡消费,强烈推荐;
  • 缺点:实现稍微复杂,其它缺点没有想到。

Redis + Lua 分布式限流:

  • 优点:支持分布式限流,有效保护下游依赖的服务资源;
  • 缺点:依赖 Redis,对边界没有很好处理,导致限流不能精准控制。

硬核推荐:

Supongo que te gusta

Origin juejin.im/post/7145435951899574302
Recomendado
Clasificación