目录
开篇介绍:
Sentinel有2种规则:
流控设置面板的介绍:
基于并发线程数进行限流配置验证
流量控制的效果有几种?
Sentinel熔断降级规则
服务调用常见的熔断状态和恢复
代码演示服务调用熔断例子-异常熔断
流控规则的异常信息怎么处理更好
【新版】实现BlockExceptionHandler并且重写handle方法
【旧版】实现UrlBlockHandler并且重写blocked方法
Feign整合Sentinel配置实现流控熔断兜底数据返回
本文讲解了流控和熔断规则的条件效果代码面板配置实际演示,以及自定义熔断降级后的错误信息和返回兜底数据,全文干货 ,值得一看~
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
一个是基于统计并发线程数的流量控制
并发数控制用来保护业务线程池不被慢调用耗尽
Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目)
如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。
还有一个是基于统计QPS的流量控制
当 QPS 超过某个阈值的时候,则采取措施进行流量控制
资源名:默认是请求路径,可自定义
针对来源:对哪个微服务进行限流,默认是不区分来源,全部限流,这个是针对 区分上游服务进行限流, 比如 视频服务 被 订单服务、用户服务调用,就可以针对来源进行限流
验证代码:
@RequestMapping("list")
public Object list(){
try {
TimeUnit.SECONDS.sleep(3);//这里睡3秒,模拟实际开发过程的业务逻辑代码
} catch (InterruptedException e) {
e.printStackTrace();
}
Map map = new HashMap<>();
map.put("title1","小王同学的sentinel验证1");
map.put("title2","小王同学的sentinel验证2");
return map;
}
设置线程数的流控规则:
请求访问浏览器:
http://localhost:8000/api/v1/video_order/list
3秒内刷新一次是正常点的,是一个线程。当3秒内刷新多次的时候,多个线程进入方法
被Sentinel监测到后,就触发了流控 返回了Blocked by Sentinel(flow limiting)
直接拒绝(用的最多):默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝
Warm Up:冷启动/预热,当系统长期处于空闲的情况下,当流量突然增加时,直接把系统拉升到很忙的阶段可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给空闲的系统一个预热的时间,避免系统被压垮。warm up冷启动主要用于启动需要额外开销的场景,例如建立数据库连接等。
匀速排队:严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,主要用于处理间隔性突发的流量,如消息队列,想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求
匀速排队等待策略是 Leaky Bucket 算法结合虚拟队列等待机制实现的。
匀速排队模式暂时不支持 QPS > 1000 的场景
流控的官方文档:https://github.com/alibaba/Sentinel/wiki/流量控制#基于调用关系的限流
官方提供的中文文档,排版一般内容还不错,可以看看
熔断降级(虽然是两个概念,基本都是互相配合)
对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一
对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩
熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
什么是Sentinel降级规则
就是配置一定规则,然后满足之后就对服务进行熔断降级
文档:https://github.com/alibaba/Sentinel/wiki/熔断降级
Sentinel 熔断策略
熔断策略分为:慢调用比例,异常比例和异常数这三种策略
下面分别介绍下:
其中慢调用比例(也就是响应时间): 选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
规则面板参数的意思:
最大RT ,超过X毫秒时间,属于慢调用
比例阈值:1.8版本修改后仅仅在列表页面不生效-目前已经反馈给官方那边的bug
熔断时长:熔断后,超过这个时间则会尝试恢复请求调用
最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断,比如说配置了10个,假设系统只有5个请求,那么即使这5个请求都达到了熔断的条件,也不会触发熔断,因为没有达到最小请求数
异常比例:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
比例阈值
熔断时长:熔断后,超过这个时间则会尝试恢复请求调用
最小请求数:熔断触发的最小请求数,请求数小于该值时,即使异常比率超出阈值也不会熔断
当请求数超过10个,且异常比例大于0.3,则接下来的熔断时间长10秒内,请求会被自动熔断
异常数:当单位统计时长内的异常数目超过阈值之后会自动进行熔断
异常数:
熔断时长:超过时间后会尝试恢复
最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
当请求数超狗5个,且异常数超过10个,则接下来的熔断时间长10秒内,请求会被自动熔断
服务熔断一般有三种状态(画图)
熔断关闭(Closed)
服务没有故障的时候,熔断器所处的关闭状态,对调用方的调用不做任何限制
熔断开启(Open)
后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
半熔断(Half-Open)-(sentinel中1.8版本后才引入的)
所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率
熔断恢复:
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。
如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断状态
int temp = 0;
@RequestMapping("list")
public Object list(){
temp++;
//模拟刷新三次中有一个失败的情况,来看下Sentinel异常熔断降级情况
if(temp%3 == 0){
throw new RuntimeException();
}
Map map = new HashMap<>();
map.put("title1","小王同学的sentinel验证1");
map.put("title2","小王同学的sentinel验证2");
return map;
}
Sentinel面板配置:
在浏览器中快速的请求这个接口,三次中会失败一次,当监测到大于2个请求,有十分之一的请求异常,就会熔断10秒然后出现下面这个错误。当过了10秒之后,又恢复了正常。
http://localhost:8000/api/v1/video_order/list
异常数的验证:(生产环境用,一般用异常比例的情况比较多)
在浏览器中快速的请求这个接口,当监测到大于3个请求,单位时间窗口异常数大于2个,就会熔断10秒。当过了10秒之后,又恢复了正常。
http://localhost:8000/api/v1/video_order/list
AlibabaCloud引入Sentinel增加流控规则后,当触发流控时,接口直接返回Blocked by Sentinel (flow limiting),这个用户体验很差,正常都是返回前后端交互的json格式,那么这里就需要自定义返回熔断发信息了。先看下下面2个问题:
默认降级返回数据问题
限流和熔断返回的数据有问题-都是Blocked by Sentinel (flow limiting)
微服务交互基本都是json格式,如果让自定义异常信息AlibabCloud版本升级,不兼容问题
v2.1.0到v2.2.0后,Sentinel里面依赖进行了改动,且不向下兼容
解决方法:
先知道下异常种类:
FlowException //限流异常
DegradeException //降级异常
ParamFlowException //参数限流异常
SystemBlockException //系统负载异常
AuthorityException //授权异常
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class WnnBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
Map info = new HashMap<>();
if(e instanceof FlowException){
info.put("code",-1);
info.put("msg","限流异常");
}
else if(e instanceof DegradeException){
info.put("code",-2);
info.put("msg","降级异常");
}
else if(e instanceof ParamFlowException){
info.put("code",-3);
info.put("msg","热点参数异常");
}
else if(e instanceof SystemBlockException){
info.put("code",-4);
info.put("msg","系统异常");
}
else if(e instanceof AuthorityException){
info.put("code",-5);
info.put("msg","授权异常");
}
//设置json返回
httpServletResponse.setStatus(200);
httpServletResponse.setHeader("content-type","application/json;charset=UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(info));
}
}
触发流控和熔断降级的返回值:
这种相比之前都是Blocked by Sentinel (flow limiting)更加友好
@Component
public class XdclassUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
//降级业务处理
}
}
到这就完成啦~加入了返回异常信息后,用户体验会更好的~
首先描述下场景:A订单服务 B视频服务,当订单服务调用视频服务的时候因某些原因触发了流控或者熔断,那么订单服务获取到视频服务的接口将会是500等不友好的信息。
比如这种:
现在可以通过Feign,给出现这种情况的调用返回一些兜底结果,代码如下:
引入依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
开启Feign对Sentinel的支持
feign:
sentinel:
enabled: true
创建容错类, 实现对应的服务接口, 记得加注解 @Service
容错类和对应的接口可以一样,容错对应的接口就可以了。内容可以按照自己想要展示的来定义
举例:
import net.wnn.domain.Video;
import net.wnn.service.VideoService;
import org.springframework.stereotype.Service;
@Service
public class VideoServiceFallback implements VideoService {
@Override
public Video findById(int videoId) {
Video video = new Video();
video.setTitle("这个是Fallback里面的视频");
return video;
}
@Override
public int save(Video video) {
return 0;
}
}
配置feign容错类:fallback = VideoServiceFallback.class
这样就有兜底数据返回啦,下游接口出错的时候,不至于返回500等太不友好了。