什么是雪崩
由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。
解决方案
熔断器模型
服务熔断是指当某个服务提供者无法正常为服务调用者提供服务时,比如请求超时、服务异常等,为了防止整个系统出现雪崩效应,暂时将出现故障的接口隔离出来,断绝与外部接口的联系,当触发熔断之后,后续一段时间内该服务调用者的请求都会直接失败,直到目标服务恢复正常。
熔断降级参考指标
服务降级需要有一个参考指标,一般来说有以下几种常见方案:
• 平均响应时间:比如1s内持续进入5个请求,对应时刻的平均响应时间均超过阈值,那么接下来在一个固定的时间窗口内,对这个方法的访问都会自动熔断。
• 异常比例:当某个方法每秒调用所获得的异常总数的比例超过设定的阈值时,该资源会自动进入降级状态,也就是在接下来的一个固定时间窗口中,对这个方法的调用都会自动返回。
• 异常数量:和异常比例类似,当某个方法在指定时间窗口内获得的异常数量超过阈值时会触发熔断。
Sentinel
Sentinel是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、服务降级、系统负载保护等多个维度来帮助我们保障微服务的稳定性。
文档
https://github.com/alibaba/Sentinel/wiki
特性
丰富的应用场景:几乎涵盖所有的应用场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制等。
• 实时监控:Sentinel提供了实时监控功能。开发者可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群汇总运行情况。
• 开源生态支持:Sentinel提供开箱即用的与其他开源框架/库的整合,例如与Spring Cloud、Dubbo、gRPC的整合。开发者只需要引入相应的依赖并进行简单的配置即可快速接入Sentinel。
• SPI扩展点支持:Sentinel提供了SPI扩展点支持,开发者可以通过扩展点来定制化限流规则,动态数据源适配等需求。
组成
Sentinel分为两个部分:
• 核心库(Java客户端):不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo、Spring Cloud等框架也有较好的支持。
• 控制台(Dashboard):基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。
启动控制台
官方 GitHub Release 页面 页面下载最新版本的控制台 JAR 包。
启动
java -Dserver.port=10006 -Dcsp.sentinel.dashboard.server=localhost:10006 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
#ubuntu
nohup java -Dserver.port=10006 -Dcsp.sentinel.dashboard.server=localhost:10006 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.6.2.jar >/dev/null 2> /dev/null &
notice:
n1.jdk环境必须是JDK1.8及以上。
n2.从Sentinel 1.6.0 起,Sentinel 控制台引入基本的 登录 功能,默认用户名和密码都是 sentinel。
访问
客户端接入
pom
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改配置文件
在 Nacos 控制台修改配置文件,增加对 Sentinel 的支持
spring:
application:
name: gulimail-order
cloud:
nacos:
discovery:
server-addr: xxx.xxx.xx.xx:8848
sentinel:
transport:
dashboard: xxx.xxx.xx.xx:10006
server:
port: 9090
修改 api接口
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.fallback.OrderFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinelFallback")
public ResponseResult testSentinel(String args){
log.info("输入参数》》》"+args);
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
测试熔断
当QPS>1时
notice:
n1. 必须至少请求过一次才能在 Sentinel 控制台看到对应的服务
n2.这个sentinel部署的服务器必须要和服务应用服务器之间网络是能够互相连接的,否则sentinel会连接不上应用的服务器,无法监控应用。
n3.每次重启项目后,流控规则清空。
在实际应用中,大都采用JSON格式的数据,所以如果希望修改触发限流之后的返回结果形式,则可以通过自定义限流异常来处理,比如限流异常和熔断异常。
限流异常
限流异常专门负责处理sentinel限流抛出的异常(BlockException)
定义限流异常处理类
package com.pl.gulimailcart.Infrastructure.sentinel.block;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName OrderBlockHandler
* @Author pl
* @Date 2021/3/12
* @Version V1.0.0
*/
@Slf4j
public class OrderBlockHandler {
public static ResponseResult<String> testSentinelFallback(String args,BlockException blockException) {
return new ResponseResult(ErrorCodeEnume.HTTP_SENTINEL.getCode(),ErrorCodeEnume.HTTP_SENTINEL.getMsg());
}
}
OrderApi
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.sentinel.block.OrderBlockHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinel",blockHandler = "testSentinelFallback",blockHandlerClass = OrderBlockHandler.class)
public ResponseResult testSentinel(String args){
log.info("输入参数》》》"+args);
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
notice:
n1.blockHandlerClass
blockHandler默认需要和原方法写在一个类中,但是若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。
n2.blockHandlerClass中的方法必须是static修饰
JAVA异常
fallback用于处理java异常
fallback类
package com.pl.gulimailcart.Infrastructure.sentinel.fallback;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName OrderFallback
* @Author pl
* @Date 2021/3/12
* @Version V1.0.0
*/
@Slf4j
public class OrderFallback {
public static ResponseResult testSentinelFallback(String args,Throwable ex){
log.warn("Invoke testSentinelFallback: " + ex.getClass().getTypeName());
ex.printStackTrace();
log.info("进入testSentinelFallback,输入参数》》》"+args);
return new ResponseResult<String>(ErrorCodeEnume.HTTP_FALLBACK.getCode(), ErrorCodeEnume.HTTP_FALLBACK.getMsg(),ex.getMessage());
}
}
OrderApi
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.sentinel.fallback.OrderFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinel",fallback = "testSentinelFallback",fallbackClass = OrderFallback.class)
public ResponseResult testSentinel(String args){
log.info("输入参数》》》"+args);
int i = 10 / 0;
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
notice:
n1.fallbackClass中的方法必须是static修饰
n2.如果项目中有配置@ControllerAdvice注解的统一异常处理,则不太需要运用fallback属性
fallback和blockHandler一起使用
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.sentinel.block.OrderBlockHandler;
import com.pl.gulimailcart.Infrastructure.sentinel.fallback.OrderFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinel",fallback = "testSentinelFallback",fallbackClass = OrderFallback.class,blockHandler = "testSentinelFallback",blockHandlerClass = OrderBlockHandler.class)
public ResponseResult testSentinel(String args){
log.info("输入参数》》》"+args);
int i = 10 / 0;
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
FQS<1
FQS>1