1.Sentinel是什么?
随着分布式系统变得越来越流行,服务之间的可靠性变得比以往任何时候都更加重要。Sentinel是强大的流控制组件,以“流”为切入点,涵盖多个领域,包括流控制,并发限制,电路中断和自适应系统保护,以确保微服务的可靠性。
一句话讲就是Spring Cloud Alibaba
用来替换之前的Hystrix
的技术。
2.有什么用?
用来做系统流量控制、熔断降级、系统的负载保护等。
3.下载并启动Sentinel
1.下载地址
2.启动
java -jar sentinel-dashboard-1.7.2.jar
3.访问Dashboard,用户名密码都是sentinel
4.测试准备
1.新建一个springboot项目
2.引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- sentinel-datasource-nacos 后续持久化用 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3.启动类添加注解@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class AlibabaSentinelServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AlibabaSentinelServiceApplication.class, args);
}
}
4.application.properties
配置
server.port=8401
spring.application.name=cloudalibaba-sentinel-service
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name}
spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.dsl.nacos.data-type=json
spring.cloud.sentinel.datasource.dsl.nacos.rule-type.=flow
management.endpoints.web.exposure.include=*
5.提供测试接口
@RestController
public class FlowLimitController {
private static Logger log = LoggerFactory.getLogger(FlowLimitController.class);
@GetMapping("/testA")
public String testA(){
// try {
// TimeUnit.MILLISECONDS.sleep(800);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return "testA-----";
}
@GetMapping("/testB")
public String testB(){
log.info(Thread.currentThread().getName() + "...testB ");
return "testB -----";
}
}
6.启动sentinel
和nacos
不懂nacos可以参考
5.测试流控规则
- 资源名:唯一名称,默认请求路径
- 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认
default
(不区分来源) - 阈值类型/单机阈值
- QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
- 线程数:当调用该api的线程数达到阈值的时候,进行限流
- 流控模式
- 直接:api达到限流条件时,直接限流
- 关联:当关联的资源达到阈值时就限流自己。
A接口与B接口关联,当B接口到达阈值,让A接口限流起到保护B接口的作用。 例如支付接口与下单接口,当支付接口到达阈值,让下单接口限流,起到保护支付接口的作用。 - 链路:只记录指定链路上的流量(指定资源入口资源进来的流量,如果达到阈值,就进行限流)
- 流控效果
- 快速失败:直接失败抛异常
- Warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
一般用于类似秒杀的功能。 - 排队等候:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。
5.1测试QPS到达阈值
配置如下:
当你连续点击看到如下限流提示(1s请求数量超过配置阈值)
5.2测试线程数到达阈值
配置如下:
为了方便测试让每个被调用的方法睡眠一会
@GetMapping("/testA")
public String testA(){
//测试线程阈值
try {
TimeUnit.MILLISECONDS.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "testA-----";
}
当你连续点击看到如下限流提示(1s请求数量超过配置阈值)
5.3测试关联限流
配置如下
用postman模拟与/testA
关联的/testB
到达阈值
postman启动之后调用/testA
可以看到该接口被限流。
5.4测试Warm up(限流冷启动)
配置如下(访问流量忽然增大时,从阈值/冷加载因子 开始经过预热时长达到每秒可访问阈值,即从流量增大时刚开始可以支持每秒2次访问,经过3s可以支持每秒6次访问接口),默认冷加载因子(coldFactor)为3
可以点击访问/testA
接口测试,开始慢慢慢点击没问题,然后加快速度会出现限流,过一会之后,限流就没有了。
5.5.排队等候
配置如下
为了方便查看调用,接口中打印线程名称
@GetMapping("/testB")
public String testB(){
log.info(Thread.currentThread().getName() + "...testB ");
return "testB -----";
}
重启项目调用,可以在控制台中看到调用记录
6.测试降级规则
- RT:
- 当资源的平均响应时间超过阈值(这里阈值即自己配置的毫秒值)且在时间窗口内通过的请求>= 5,两个条件同时满足触发熔断降级
- 窗口期后关闭断路器。
- RT最大4900(更大通过 -Dcsp.sentinel.statistic.max.rt=xxx 来配置)
- 异常比例:
QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级。 - 异常数:
异常数(分钟统计)超过阈值,触发降级;时间窗口结束后,关闭降级
Sentienl熔断降级会在调用调用链路中某个资源出现不稳定时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误。
当资源降级后,在接下来的降级时间窗口之内,对该资源的调用自动熔断(默认行为是抛出DegradeException)
6.1测试RT
1.添加测试接口
@GetMapping("/testD")
public String testD(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "testD -----";
}
为了让平均响应时间增大,在代码中加入sleep休眠。
2.利用JMater进行压力测试
1s 10个线程访问/testD
接口
3.配置如下
4.启动JMeter,自己调用/testD
,可以看到该接口已被熔断降级。
熔断分析:1.平均响应时间(1000ms)超出阈值(200ms) 2.在时间窗口内通过请求10*5=50>=5。满足两个条件,所以发生熔断降级
6.2测试异常比例
资源每秒请求量>=5&&每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的时间窗口之内,对这个方法的调用都会自动的返回(服务熔断)。异常比率阈值范围[0.0,1.0],代表0%-100%。
1.提供测试异常比例接口
@GetMapping("/testException")
public String testException(){
log.info("testException 异常比例");
int age = 10 /0 ;
return "testException -----";
}
2.利用JMater进行压力测试,除接口外其他配置同上面测试RT
3.sentinel配置
4.重启项目并启动JMeter进行测试,调用/testException
可以服务熔断提示信息
6.3测试异常数
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若时间窗口小于60s,则结束熔断状态后可能再进入熔断状态。
1.提供测试接口
@GetMapping("/testExceptionCount")
public String testExceptionCount(){
log.info("testExceptionCount 异常数");
int age = 10 /0 ;
return "testExceptionCount -----";
}
2.sentinel配置
3.重启项目进行测试
前面5次访问都报错,后面访问进入熔断后降级。
熔断后降级如下图
7.热点参数限流
7.1普通热点参数限流
1.提供接口进行测试
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2){
return "testHotKey -----";
}
public String dealTestHotKey(String p1, String p2, BlockException blockException){
return "dealTestHotKey---------";
}
2.sentinel配置
上面的配置第一个参数p1,当QPS超过1秒1次点击后马上被限流。
3.重启项目测试
7.2参数例外项热点限流
1.sentinel配置如下
以上配置含义,当第一个参数p1的值为5时接口/testHotKey
的流量阈值为200
注意:
@SentinelResource处理的是控制台配置的违规情况,有blockHandler方法配置的兜底处理。
但是@SentinelResource不管代码中出现的运行时期异常(RuntimeException)
8.@SentinelResource配置
8.1按资源名称或URL地址限流加后续处理
1.提供接口
/**
* (违反sentinel配置)手动配置兜底处理blockHandler
* @return
*/
@GetMapping(value = "/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200, "按资源名称限流测试OK");
}
public CommonResult handleException(BlockException blockException){
return new CommonResult<>(444, blockException.getClass().getCanonicalName()+"\t服务不可用" );
}
/**
* 默认处理
* @return
*/
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl(){
return new CommonResult(200, "by url限流测试OK");
}
2.sentinel配置
资源名称:
url:
3.重新启动测试
访问byResource
当每秒访问超过1次时,使用自定义的兜底处理。
访问/rateLimit/byUrl
当每秒访问超过1次时,使用默认处理。
以上配置存在问题:
- 1.系统默认的,没有体现我们自己的业务需求
- 2.依照现有条件,我们自定义的处理方法和业务代码耦合在一块,不直观
- 3.每个业务方法都有一个兜底的,代码膨胀加剧
- 4.全局同一处理方法没有体现
8.1解决存在的问题
1.提供接口
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception) {
return new CommonResult(444, "客户自定义,global handlerException---1");
}
public static CommonResult handlerException2(BlockException exception) {
return new CommonResult(444, "客户自定义,global handlerException---2");
}
}
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200, "客户自定义 限流测试OK");
}
2.sentinel中配置
3.重启测试
9.整合Ribbon和OpenFeign
9.1准备
1.创建三个springboot项目,分别为alibaba-consumer2、alibaba-provider3、alibaba-provider4(alibaba-provider3/alibaba-provider4一样)
由于东西过多这里不罗列,详细参考https://github.com/xiaoxiaoshou/springclouddemo
9.2Ribbon系列
主要示例代码:
@RestController
@Slf4j
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class CircleBreakerController {
private static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback") //没有配置
// @SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只负责业务异常
// @SentinelResource(value = "fallback",blockHandler = "blockHandler") // 配置了blockHandler,只负责sentinel控制台配置违规
@SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler") // 配置了blockHandler和fallback
// @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) // 忽略运行时IllegalArgumentException异常不进行自定义处理
public CommonResult<Payment> fallback(@PathVariable("id") Long id){
CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
}else if(commonResult.getData() == null){
throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
}
return commonResult;
}
// 本例是fallback
public CommonResult handlerFallback(Long id, Throwable e){
Payment payment = new Payment(id, null);
return new CommonResult(444, "兜底异常handler,exception内容"+e.getMessage(), payment);
}
public CommonResult blockHandler(Long id, BlockException exception){
Payment payment = new Payment(id, null);
return new CommonResult<>(445, "blockHandler-sentinel 限流,无此流水号:blockException" + exception.getMessage(), payment);
}
}
fallback
对应方法(handlerFallback方法)处理代码运行时期异常blockHandler
对应方法(blockHandler)处理Sentinel中违规exceptionsToIgnore
忽略运行时期某个异常不进行自定义处理
配置Sentinel(对资源fallback阈值为1)并启动项目测试,可以看到既可以处理调用过程中对应Sentinel违规也可以处理运行时期异常。
9.3 OpenFeign系列
1.主要配置
#激活sentinel对feign的支持
feign:
sentinel:
enabled: true
2.绑定对应服务
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping("/paymentSQL/{id}")
CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444, "fallback");
}
}
3.提供访问接口
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return paymentService.paymentSQL(id);
}
4.重启项目即可冲过consumer接口调用第三方接口
各熔断器的比较:
10.规则持久化
当你每次重启对应服务,你会发现在Sentinel中配置对应的规则就没有了,在生产环境中我们需要配置规则的持久化(持久化工具都可以,官方推荐Nacos)。
1.引入依赖
<!-- sentinel-datasource-nacos 后续持久化用 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.配置文件配置
spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name}
spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.dsl.nacos.data-type=json
spring.cloud.sentinel.datasource.dsl.nacos.rule-type=flow
3.在nacos中配置
[
{
"resource":"/rateLimit/byUrl",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
配置项含义:
- resource:资源名称
- limitApp:来源应用
- grade:阈值类型,0表示线程数,1表示QPS
- count:单机阈值
- strategy:流控模式,0表示直接,1表示级联,2表示链路
- controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等候
- clusterMode:是否集群