业务学习 -- 高并发系统保护之限流和降级熔断

简介

目前在分布式系统开发中随着业务的增长,业务与业务之间的隔离关系越来越明确,限流目前也是在分布式系统中常见的需求。
限流的出发点可能基于以下考虑:
(1)简单来说就是控制流程,防止因为请求流量过大导致服务崩溃。
(2)通过限流设计可以限制不同的接口及服务的优先级关系,在资源一定时优先提供重要的接口及服务。
(3)流量整理,针对流程进行削峰填谷整理。

限流方式

目前常见的限流方式有如下几种:
(1)请求入口限流算法:线程池及等待队列(Tomcat)、漏桶算法、令牌桶算法和计数器算法
(2)接口调用限流实现方式:线程池和信号量
(3)MQ 流量削峰填谷

限流算法:
1、令牌桶算法
在这里插入图片描述
算法思想是:

  • 令牌以可调控速率产生,并缓存到令牌桶中;
  • 令牌桶放满时,多余的令牌被丢弃;
  • 请求要消耗等比例的令牌才能被处理;
  • 令牌不够时,请求被缓存。

2、漏桶算法
在这里插入图片描述
算法思想是:

  • 水(请求)从上方倒入水桶,从水桶下方流出(被处理);
  • 来不及流出的水存在水桶中(缓冲),以固定速率流出;
  • 水桶满后水溢出(丢弃)。
  • 这个算法的核心是:缓存请求、匀速处理、多余的请求直接丢弃。
  • 相比漏桶算法,令牌桶算法不同之处在于它不但有一只“桶”,还有个队列,这个桶是用来存放令牌的,队列才是用来存放请求的。

3、计数器算法

计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:
在这里插入图片描述

限流实现

Nginx 限流

Nginx主要有两种限流方式:
(1)按连接数限流(ngx_http_limit_conn_module)(令牌算法实现)
(2)按请求速率限流(ngx_http_limit_req_module)(漏桶算法实现)

ngx_http_limit_req_module

模块提供限制请求处理速率能力,使用了漏桶算法(leaky bucket)。下面例子使用 nginx limit_req_zone 和 limit_req 两个指令,限制单个IP的请求处理速率。在 nginx.conf http 中添加限流配置:

格式:limit_req_zone key zone rate
http {
limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=10r/s;
}

配置 server,使用 limit_req 指令应用限流。

server {
location / {
limit_req zone=myRateLimit;
proxy_pass http://my_upstream;
}
}

key :定义限流对象,binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
zone:定义共享内存区来存储访问信息, myRateLimit:10m 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。
rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。

burst
上面例子限制 10r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。

server {
location / {
limit_req zone=myRateLimit burst=20;
proxy_pass http://my_upstream;
}
}

burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数。当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。

此处,burst=20 ,若同时有21个请求到达,Nginx 会处理第一个请求,剩余20个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。若请求数大于21,将拒绝处理多余的请求,直接返回503.

不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。

因此,burst 往往结合 nodelay 一起使用。

server {
location / {
limit_req zone=myRateLimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
}

nodelay 针对的是 burst 参数,burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms一个来释放。

这就达到了速率稳定,但突然流量也能正常处理的效果。

ngx_http_limit_conn_module

ngx_http_limit_conn_module 提供了限制连接数的能力,利用 limit_conn_zone 和 limit_conn 两个指令即可。下面是 Nginx 官方例子:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
...
limit_conn perip 10;
limit_conn perserver 100;
}

limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。

limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。

需要注意的是:只有当 request header 被后端server处理后,这个连接才进行计数。

Tomcat 线程池限流

tomcat 通过以下三个配置参数来进行限流操作:
(1)maxThreads(最大线程数):每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务可以同时处理多少个请求,默认200.
(2)accepCount(最大等待数):当调用Web服务的HTTP请求数达到tomcat的最大线程数时,还有新的HTTP请求到来,这时tomcat会将该请求放在等待队列中,这个acceptCount就是指能够接受的最大等待数,默认100.如果等待队列也被放满了,这个时候再来新的请求就会被tomcat拒绝(connection refused)。
(3)maxConnections(最大连接数):这个参数是指在同一时间,tomcat能够接受的最大连接数。一般这个值要大于maxThreads+acceptCount。

    <Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" acceptCount="500" maxThreads="400" />

Spring Cloud Hystrix

Spring Cloud Hystrix 是 Spring Cloud 全家桶系列中提供的限流及熔断框架,其提供了基于线程池和信号量的熔断机制
1、通过配置隔离策略来选择是使用线程池还是信号量模式,默认线程池方式。

//信号量模式
ExecutionIsolationStrategy.SEMAPHORE
//线程池方式
ExecutionIsolationStrategy.THREAD

2、信号量和线程模式比较:
(1)线程池隔离模式:使用一个线程池来存储当前请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求先入线程池队列。这种方式要为每个依赖服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
(2)信号量隔离模式:使用一个原子计数器(或信号量)记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
在这里插入图片描述
3、基于线程池或信号量对接口进行限流及降级配置示例

@HystrixCommand(fallbackMethod = "getDefaultUserName", threadPoolKey = "query_user",
        threadPoolProperties = {
                @HystrixProperty(name = CORE_SIZE, value = "10"),
                @HystrixProperty(name = MAX_QUEUE_SIZE, value = "10")
        },
        commandProperties = {
                @HystrixProperty(name = CIRCUIT_BREAKER_ENABLED, value = "true"),
                @HystrixProperty(name = CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "1000"),
                @HystrixProperty(name = CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "25"),
				@HystrixProperty(name = "execution.isolation.strategy", value = "thread")
        }
)
static String getUserName(String userID) throws InterruptedException {
  Thread.sleep(-1);
  return userID;
}
public String getDefaultUserName(String userID) {
  return "";
}

Spring Cloud Hystrix 接口调用限流及熔断流程:
在这里插入图片描述

MQ 削峰填谷

目前 MQ(kafka、RocketMQ和RabbitMQ) 常被我们用来服务之间解耦、通知,但是目前比较常用的场景也是对流量进行削峰填谷,首先MQ 会对接收的消息持久化,尽最大程度的处理请求并持久化消息,消费者会根据自己的业务特别基于推或者拉模式来进行消息消费,在一定程度上也是能够在高并发下对系统进行保护。

基于Redis 实现限流

1、计数算法:
基于redis的incrby 加一和decrby 操作,来控制分布式系统中计数器。

2、令牌桶算法:

基于 Redis 的 list接口可以实现令牌桶令牌补充和令牌消耗操作。

参考:
https://www.cnblogs.com/biglittleant/p/8979915.html
https://juejin.im/post/5b3a25e46fb9a024fc284de4

https://blog.wangqi.love/articles/Java/基于redis的分布式限流方案.html

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页