一、Sentinel
1.1 雪崩问题

当一个服务依赖多个其他服务时(如上图),当服务D出故障时,无法响应服务A的请求,服务阻塞,不会释放tomcat连接。
当服务A中有多个依赖于服务D的请求传来时,会占用多个tomcat连接,最终导致tomcat资源耗尽:
当在一个微服务链路中,一个服务出故障可能导致整个链路的所有服务都不可用(多米诺骨牌),这就是雪崩。
1.1.1 解决雪崩问题的常见方式
(1)超时处理
设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待(不是根本解决办法,当超时时间内传来大量请求时还是会导致雪崩)。
(2)舱壁模式
给每个业务设置线程池,限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
例如,上图中服务A的业务1和2都使用线程池,设置线程数量为10,当服务C出故障时,服务A最多向服务C发送10个请求,不会导致tomcat资源耗尽。
注意:该方法解决了雪崩问题,但是降低了资源利用率。
(3)熔断降级
由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
(4)流量控制(sentinel)
限制业务访问的QPS,避免服务因流量的突增而故障。该方法可以按照服务所能承受的频率释放请求。
1.2 服务保护技术对比

隔离策略:线程池隔离类似于舱壁模式。信号量隔离是不会为业务创建线程池,而是统计当前业务使用的线程数,限制数量,当达到阈值后再阻止。
1.3 Sentinel
官网地址:https://sentinelguard.io/zh-cn/index.html
1.3.1 安装sentinel控制台
powershell
java -jar sentinel-dashboard-1.8.1.jar
sentinel内部提供了很多配置项,可以在运行jar修改参数信息:
1.3.2 导入项目cloud-demo
(springcloud篇1、篇2的项目)

1.3.3 微服务整合sentinel

(一)order-service服务中引入sentinel依赖

xml
<!-- 引入sentinel依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
(二)order-service配置文件中配置sentinel控制台地址
yaml
sentinel:
transport:
dashboard: localhost:8080 #sentinel控制台地址
(三)访问微服务的任意端点,触发sentinel监控
确保user-service、order-service、sentinel与nacos服务启动成功。
二、sentinel限流规则
之前提到的雪崩问题提出了四种解决方案,sentinel 可以实现其中的三种(流量控制、舱壁模式和熔断降级)。
2.1 簇点链路
簇点链路:就是项目中的调用链路(如从controller到service到mapper),链路中被监控的每个接口就是一个资源。默认情况下sentinel会监控SpringMVC的每一个端点,因此SpringMVC的每一个端点就是调用链路中的一个资源。
2.1.1 sentinel限流规则设置
对于1.3.3中微服务整合sentinel的测试中,一次http://localhost:8083/order/103的请求,sentinel的簇点链路监控如下:
流控、熔断等都是针对簇点链路中的资源来设置的,从上图中可以看到,我们可以通过点击对应资源后面的按钮来设置规则。
比如,点击"流控"按钮:
资源名 默认。
针对来源 :设置从哪里发来的请求需要被限制,default表示一切进来的请求都要被限制。
阈值类型 :一般选QPS(并发量,即每秒钟请求的数量)。
单机阈值:即QPS的上限(为1即表示每秒钟最多处理一个请求,超出的请求会被拦截并报错)。
2.1.2 案例
(一)sentinel设置流控规则
(二)使用jmeter进行测试
打开学习资料中已经写好的测试计划:
上图中的第一个测试计划为:2秒钟发送20次请求(QPS为10)。
右键启动:
补充1:sentinel限流规则高级模式
1.流控模式
(1)直接(默认)
统计当前资源的请求,触发阈值时对当前资源直接限流。
(2)关联
统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流。
关联模式使用场景:
配置方法:
java
@GetMapping("/query")
public String queryOrder() {
return "查询订单成功";
}
@GetMapping("/update")
public String updateOrder() {
return "更新订单成功";
}
重启服务,在浏览器中分别发送请求:
在上述请求运行时去访问query:
可以看到,query请求被sentinel限制了。
(3)链路
统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流。
案例如下:
java
public void queryGoods(){
System.err.println("查询商品");
}

java
@GetMapping("/query")
public String queryOrder() {
// 查询商品
orderService.queryGoods();
// 查询订单
System.err.println("查询订单");
return "查询订单成功";
}
@GetMapping("/save")
public String saveOrder() {
// 查询商品
orderService.queryGoods();
// 查询订单
System.err.println("新增订单");
return "新增订单成功";
}
注意:
(1)sentinel只默认标记controller中的方法为资源,如果要标记其他方法(比如service中的方法),需要使用@SentinelResource注解。
(2)sentinel默认会将controller方法做context整合,导致链路模式的流控失效,需需要修改sentinel配置:
yaml
web-context-unify: false # 关闭context整合
注意:上图中的goods时同一个资源,任意选择一个进行配置即可。
启动上面的测试任务:
2.流控效果
流控效果是指请求达到流控阈值时应该采取的措施,包括3种。
(1)快速失败(默认)
指达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。
(2)warm up
预热模式,对超出阈值的请求同样是拒绝并抛出异常,但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
是应对服务冷启动(服务刚启动就达到阈值可能会使服务响应不了)的一种方案。请求阈值初始值是threshold/coldFactor(coldFactor默认为3),持续指定时长后,逐渐提高到threshold值。
一个案例:
(3)排队等待
让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长。设置一个超时时间,若是在队列中等待的时间超时,则会抛出异常。
一个案例:
可以看到接收的QPS是稳定的。
补充2:热点参数限流
之前的限流都是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。
一个例子:
注意:热点参数限流对springmvc默认资源无效,需要加@SentinelResource注解。
结果如下:
注意:单机阈值为2。
三、 隔离和降级
3.1 feign与sentinel整合
隔离和降级即舱壁模式(线程隔离)。
从前面可知,不论是线程隔离还是熔断降级,都是对客户端(调用方)的保护。springcloud中远程调用使用feign,所以线程隔离和熔断降级使用feign整合sentinel 的模式。
步骤:
(一)在调用者(order-service,被调用者是user-service)中开启feign对sentinel的支持
yaml
sentinel:
enabled: true # 开启feign对sentinel的支持
(二)给FeignClient(feign-api服务)编写失败后的降级逻辑FallBackFactory
java
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
log.error("查询用户异常", throwable);
return new User();
}
};
}
}
回忆一下之前写的UserClient:
(三)注册写好的FallbackFactory
java
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
(四)在Userlient注解上声明FallbackFactory
3.2 线程隔离
线程隔离有两种实现方式:
(1)线程池隔离
(2)信号量隔离(sentinel默认采用)
不会创建新的线程,使用原来处理请求的线程。一个请求信号量的计数器减1,业务处理完信号量加1,计数器为0时再有请求会被拒绝。
一个案例:
3.3 熔断降级
由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。
熔断器熔断(closed状态到Open状态即2)策略有三种:慢调用、异常比例、异常数
3.3.1慢调用

一个例子:
java
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) throws InterruptedException {
if(id==1){
//休眠60毫秒,触发熔断
Thread.sleep(60);
}
return userService.queryById(id);
}
执行完jmeter测试任务后,访问102查不到用户,即触发了熔断:
3.3.2异常比例和异常数
java
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) throws InterruptedException {
if(id==1){
//休眠60毫秒,触发熔断
Thread.sleep(60);
}else if(id==2){
throw new RuntimeException("故意出错,触发熔断");
}
return userService.queryById(id);
}
降级策略按例子配置,这里不在举例了。
四、授权规则
4.1 sentinel授权规则配置
针对绕过网关直接访问微服务的情况(当然这是异常情况,只是多加一层保护)。
(一)新增继承RequestOriginParser的类
java
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request){
// 1.获取请求头
String origin = request.getHeader("Origin");
//2.非空判断
if(StringUtils.isEmpty(origin)){
origin = "blank";
}
return origin;
}
}
(二)修改gateway服务,给所有请求增加请求头origin
yaml
default-filters:
- AddRequestHeader=origin,gateway
重启order-service服务和gateway服务。
(三)在sentinel控制台添加授权规则
4.2 自定义异常结果
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口。
可以通过判断BlockException的异常类型从而进行不同的处理,异常类型包括:
(一)添加继承BlockExceptionHandler的类
java
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if (e instanceof FlowException) {
msg = "请求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "请求被热点参数限流";
} else if (e instanceof DegradeException) {
msg = "请求被降级了";
} else if (e instanceof AuthorityException) {
msg = "没有权限访问";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}
重启服务进行测试:
4.3 规则持久化
服务重启时,配置与该服务相关的sentinel规则就会消失,这是因为sentine规则l默认保存在内存,生产环境需持久化。
4.3.1 pull模式

4.3.2 Push模式

push模式实现较为复杂,依赖于nacos,并且需要修改sentinel控制台源码。
具体步骤见资料《sentinel规则持久化.md》: