概念
熔断
在分布式系统中,服务与服务之间往往是相互依赖的,如果一个服务崩溃了,可能会影响到该服务所在的链路的其他服务,如果没有进行有效的隔离措施的话,可能会导致这个故障蔓延到整个系统,导致系统的雪崩
熔断则类似电力系统的保险丝,当电流过载时,就会触发保险丝的熔断机制,保护其他电路不受损害。
服务熔断是指当某个服务无法正常为服务调用者提供服务时,比如请求超时,服务异常等,为了防止整个系统出现雪崩效应,暂时将出现故障的接口隔离出来,断绝与外部接口的联系,当触发熔断之后,后续一段时间内该服务调用者的请求都会直接失败(无需等待到超时),直到目标服务恢复正常
熔断机制能够在服务故障时及时切断调用链,防止故障扩散,减少对故障服务的资源消耗,保护系统资源
不被耗尽,从而保护系统的整体稳定性和可用性。
限流
限流就是限制流量,在互联网应用中,限流可以确保服务器能够处理的请求数量在合理范围内,避免因请求过多导致服务响应慢或失败.可以保证用户在高流量时段仍然能够获得快速响应,提升用户体验。
本文将以sentinel实现限流与熔断的效果
Sentinel
在需要监控的服务添加下面的依赖:
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
xml
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
client-ip: 127.0.0.1 #连接本地Sentinel 可以不配置, 连接服务器Sentinel需要配置⼀下client-ip
当我们进行访问的时候可以在实时监控这个面板下看到当前QPS的通过数以及拒绝的数量

在簇点链路也可以看,簇点链路:是指在微服务架构中,请求从进入服务到处理完成所经过的完整调用链路,当请求进入服务时,首先会访问DispatcherServlet,然后进入Controller,Service,Mapper,这样的一个调用链就叫做簇点链路。
默认情况下,Sentinelstarter会为SpringMVC的所有HTTP服务提供限流埋点,所以如果只想对HTTP服务进行限流,那么只需要添加依赖即可,不需要修改任何代码,如果想要对特定的方法进行限流或者降级,则可以自定义资源来实现流控,熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则

限流设置

并发线程数限流用于保护业务线程数不被耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对高线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离),或者使用信号量来控制同时请求的个数(信号量隔离)。这种隔离方案虽然能够控制线程数量,但无法控制请求排队时间。当请求过多时排队也是无益的,直接拒绝能够迅速降低系统压力。Sentinel线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程个数,如果超出阈值,新的请求会被立即拒绝。
我们可以通过QPS来限制每秒最大请求量,在单机阈值填写对应的值即可。
高级设置------流控效果

流控效果:
快速失败:超过QPS的请求会直接响应失败
Warm Up:

Warm Up 也可以叫做预热模式,设置的单机阈值也是当前服务能承受的最大的QPS,该设置模式是当系统长期处于低水位的情况下,当流量突然增加的话,直接将系统接收最大的QPS,可能会导致系统崩溃,我们通过冷启动的方式,在指定的预热时长,让流量缓慢增加,最后达到最大的QPS,上面的预热时长设置为10,单机阈值为10,在10s的时间内将系统的QPS增加到10QPS

我们通过jmeter设置一下请求:线程数200,Ramp-Up时间10s,表示10s内发送200个请求,粗略估计QPS为20,然后我们通过同步定时器保证一下是20个请求同步发送,用户数20,表示等待20个线请求之后一起发送



我们可以看到QPS逐渐增加到10QPS
排队等待:

排队等待的方式控制了请求通过的间隔时间,让请求以均匀的速度通过,可以理解为将所有的请求放入到一个队列中,按照时间隔间通过对应的请求个数,后面的请求必须等待前面执行完成,直到超时。

高级设置------流控模式

直接:当流量超过设置的QPS,会对当前的接口限流,只要调用这个接口就会对这个接口进行限流,无论是谁调用。
链路:
我们先来构建链路:

java
@RequestMapping("/read/{productId}")
public ProductInfo read(@PathVariable("productId") int productId) {
return productService.selectProductById(productId);
}
@RequestMapping("/write/{productId}")
public ProductInfo write(@PathVariable("productId") int productId) {
return productService.selectProductById(productId);
}
因为Sentinel默认是对HTTP请求进行监控,因此要监控到Service层的方法需要自定义sentinel资源,使用@SentinelResource
java
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@SentinelResource("selectProductById")
public ProductInfo selectProductById(Integer id){
return productMapper.selectProductById(id);
}
}
链路模式中,是对不同来源的两个链路做监控,但是sentinel默认会给进入SpringMvC的所有请求设置同一个root资源,会导致链路模式失效.我们需要关闭这种对SpringMVC的资源聚合,修改配置如下:


对write资源进行限流,我们启动jmeter,设置QPS为20:


关联:
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的

介绍一下上面的配置:首先我们对read 资源进行限流,关联资源为write, 当write 的QPS超过设置的 5,触发限流,为保证write 正常运行,限制read ,优先处理 write 接口
这个限流规则的应用场景:在创建订单和读取订单信息中,如果流量过大,我们有限保证订单的创建,对读取订单这个资源进行限流。
热点参数限流
在微博热搜这种场景中,大量用户输入同一关键词,可能会导致服务的崩溃,为了避免这一情况的发生,我们可以针对参数进行限流。

单机阀值 :对第一个参数的值进行统计,当相同值参数阈值超过5时,进行限流
参数例外项 :针对一些特殊的参数,进行单独设置
参数类型 :热点参数的类型,只支持int,double,String,long,float,char,byte
参数值 :具体的参数值
限流阀值:对这个参数进行额外的阈值设置
设置后的效果图:
对于10010 进行了QPS = 5 的限流

对于 其他参数的限流则设置在 1 秒内相同的参数访问次数限制在 10 条

限流算法
计数器
在指定的周期内,当访问的次数达到设置的阈值时,就会触发限流,每到一个新周期访问次数就会清零
存在的问题:
首先维护一个计数器,在指定周期内,累加访问次数,当访问次数达到设定的阈值时,触发限流策略,当进入下一个时间周期时,将访问次数清零,这个时间周期,就可以理解为一个窗口,计数器记录这个窗口接收请求的次数。

如图所示:限定了一分钟能够处理的请求数为100,在第一个一分钟内,共请求了60次.第二个一分钟,counter又从0开始计数,在一分半钟时,已经达到了最大限流的阈值,这个时候后续的所有请求都会被拒绝。
这种算法可以用在短信发送的频次限制上,比如限制同一个用户一分钟之内触发短信发送的次数。

这种算法存在一个临界问题,如图所示,在第一分钟的0:58和第二分钟的1:02这个时间段内,分别发出了100个请求,整体来看就会出现4秒内总的请求量达到200,超出了设置的每分钟100的阈值
滑动窗口
为了解决计数器算法带来的临界问题,所以引入了滑动窗口算法,滑动窗口是一种流量控制技术,在TCP网络通信协议中,就采用了滑动窗口算法来解决网络拥塞的情况。
简单来说,滑动窗口算法的原理是在固定窗口中分割出多个小时间窗口,分别在每个小时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口,最终只需要统计滑动窗口范围内的所有小时间窗口总的计数即可。

如图所示,我们将一分钟拆分为4个小时间窗口,每个小时间窗口最多能够处理25个请求并且通过虚线
框表示滑动窗口的大小(当前窗口的大小是2,也就是在这个窗口内最多能够处理50个请求)同时滑动窗
口会随着时间往前移动,比如前面15s结束之后,窗口会滑动到15s~45s这个范围,然后在新的窗口中重新
统计数据,这种方式很好地解决了固定窗口算法的临界值问题。
Sentinel就是采用滑动窗口算法来实现限流的.
漏桶算法
漏桶算法面对限流就更加柔和,不存在直接粗暴地拒绝,漏桶算法的主要作用是控制数据注入网络的速度,平滑网络的突发流量
它的流量很简单,可以认为就是注水漏水的过程,往漏桶里注水,以固定的速率流出水,当请求超过漏桶的容量的时候就会选择丢弃

缺点:
1.漏桶算法是以固定的速率处理请求的,所以当流量突然增加的时候,服务器无法快速进行响应和处理,超过漏桶容量的请求会被丢弃,用户的体验随之降低
2.由于漏桶的流出速率是固定的,也就是说即使在流量很小的情况下,服务器响应的时间也不会缩短。
令牌桶算法
对于漏桶算法的缺点,这里可以采用令牌桶算法来实现

如图所示,系统会以一个恒定速度往固定容量的令牌桶中放入令牌,如果此时有客户端请求过来,则需要先从令牌桶中拿到令牌以获得访问资格。
假设令牌生成速度是每秒10个,也就等同于QPS=10,在请求获取令牌的时候,会存在三种情况:
1.请求速度>令牌生成速度:令牌会很快被取完,后续再进来的请求会被限流
2.请求速度=令牌生成速度:流量处于平稳状态。
3.请求速度<令牌生成速度:说明系统的并发数不高,请求能被正常处理。
由于令牌桶有固定的大小,当请求速度小于令牌生成速度时,令牌桶会被填满,所以令牌桶能够处理突发流量,也就是在短时间内新增的流量系统能够正常处理,这是令牌桶的特性。