目录
概述
环境说明
步骤
Sentinel服务端
Sentinel客户端
依赖
在客户端配置sentinel参数
测试
保护规则设置
设置资源名
设置默认的熔断规则
RestTemplate的流控规则
Feign的流控规则
概述
微服务有很多互相调用的服务,构成一系列的调用链路,如果调用链路中某个服务失效或者网络堵塞等问题,而有较多请求都需要调用有问题的服务时,这是就会造成多个服务的大面积失效,造成服务“雪崩”效应。
服务“雪崩”的根本原因在于服务之间的强依赖,为了预防服务“雪崩”这一问题,可以做好服务隔离、服务熔断降级、服务限流。
服务隔离:当某个服务故障时,不波及其他模块,不影响整体服务。
服务熔断:当下游服务因为请求压力过大造成响应慢或响应失败时,上游服务为了保护系统,暂时切断对下游服务的调用,直接返回一个降级的内容,从而保全整体系统。
服务限流:限制系统的输入和输出达到保护系统的目的,例如:限制请求速率,超出的请求不处理或者暂缓处理或降级处理。
本文介绍的服务熔断组件是Sentinel
Sentinel和Hystrix的对比
对比项目 | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断策略 | 基于响应时间或失败比例 | 基于失败比例 |
实时指标实现 | 滑动窗口 | 信号量隔离 |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、秒级监控、机器发现等 | 不完善 |
本文的操作是在 服务熔断保护实践--Hystrix 的基础上进行。
环境说明
jdk1.8
maven3.6.3
mysql8
spring cloud2021.0.8
spring boot2.7.12
idea2022
步骤
Sentinel服务端
下载Sentinel服务jar包
cmd进入jar包所在目录
使用java -jar命令启动
D:\soft\sentinel>java -jar sentinel-dashboard-1.8.6.jar
浏览器访问localhost:8080
输入用户名/密码:sentinel/sentinel
进入到Sentinel控制台
Sentinel客户端
依赖
在父工程声明spring-cloud-alibaba依赖(注意:在dependencyManagement里声明)
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
在sentinel客户端的服务(order-service、order-service-feign_hystrix) 引入sentinel依赖
<!--引入sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
刷新依赖
在客户端配置sentinel参数
application.yml配置sentinel服务控制台信息(涉及到的客户端都加,这里在order-service服务、order-service-feign_hystrix服务里加)
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
测试
启动eureka、product、order-service、order-service-feign_hystrix服务
浏览器访问
http://localhost:9002/order/buy/1
http://localhost:9003/order/buy/1
查看sentinel控制台,首页下方看到两个服务
展开,访问子选项
浏览器多次访问服务,能看到实时监控如下
保护规则设置
在order-service服务中,新建一个Controller类,用于sentinel流控测试
由OrderController复制、修改得到Order1Controller
package org.example.order.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.example.order.entity.Product;
import org.example.order.feign.ProductFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order1")
public class Order1Controller {
@Autowired
private ProductFeignClient productFeignClient;
@RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id){
return productFeignClient.findById(id);
}
}
Order1Controller添加降级方法
/**
* 定义降级逻辑
* 熔断执行的降级方法
*/
public Product orderBlockHandler(Long id){
Product product = new Product();
product.setProductName("触发熔断的降级方法");
return product;
}
/**
* 定义降级逻辑
* 抛出异常执行的降级方法
*/
public Product orderExceptionHandler(Long id){
Product product = new Product();
product.setProductName("抛出异常执行的降级方法");
return product;
}
在请求的方法上方添加@SentinelResource
注解
/**
* @SentinelResource
* blockHandler: 声明熔断时执行的降级方法(限流熔断降级)
* fallback: 抛出异常执行的降级方法(异常降级)
*/
@SentinelResource(blockHandler = "orderBlockHandler", fallback = "orderExceptionHandler")
@RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id){
return productFeignClient.findById(id);
}
修改product服务的findById方法,注释掉模拟网络延迟的代码
启动eureka、product、order服务
多次访问order1接口方法
http://localhost:9002/order1/buy/1
浏览器访问Sentinel控制台
http://localhost:8080/#/dashboard/metric/service-order
看到实时监控数据如下
熟悉相关流控规则设置界面
点击如图+流控
按钮添加流控规则,看到如下界面
点击+熔断
按钮,看到如下界面
点击+热点
按钮,看到如下界面
点击+授权
按钮,看到如下界面
修改order-service服务Order1Controller类的findById方法,添加如下代码
if(id != 1){
throw new RuntimeException("异常的id,抛出异常");
}
重启order-service服务
浏览器访问
http://localhost:9002/order1/buy/1
刷新sentinel控制台
点击添加熔断规则
访问:正常访问
http://localhost:9002/order1/buy/1
访问:异常访问
http://localhost:9002/order1/buy/2
触发异常降级方法,按住Ctrl + R
组合键,将异常访问次数超过设置阈值1
5s(熔断时长)内,访问id为正常值1,也返回的是异常降级方法
5s(熔断时长)后,访问id为正常值1,能正常访问了
设置资源名
资源名称默认为包名+类名+方法名
如何自定义资源名称?方法如下:
修改接口方法@SentinelResource
添加value= "findById"
设置
/**
* @SentinelResource
* blockHandler: 声明熔断时调用的降级方法
* fallback: 抛出异常执行的降级方法
* value: 自定义资源名称,默认 包名+类名+方法名
*/
@SentinelResource(value = "findById",blockHandler = "orderBlockHandler", fallback = "orderExceptionHandler")
@RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id){
重启order服务
发现资源名称变为了自定义的findById
同时发现一个问题:在重启order服务后也清空了熔断规则问题
可以设置默认的熔断规则来解决这个问题。
设置默认的熔断规则
在order-service服务里配置默认的熔断规则
修改application.yml,添加如下sentinel的datasource配置
spring:
cloud:
sentinel:
datasource:
ds:
file:
file: classpath:flowrule.json
data-type: json
rule-type: flow
在resources目录下添加flowrule.json文件
[
{
"resource": "orderFindById",
"controlBehavior": 0,
"count": 1,
"grade": 1,
"limitApp": "default",
"strategy": 0
}
]
一条限流规则主要由下面几个因素组成:
resource:资源名,即限流规则的作用对象 count: 限流阈值 grade: 限流阈值类型(QPS 或并发线程数) limitApp: 流控针对的调用来源,若为 default 则不区分调用来源 strategy: 调用关系限流策略 controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
这些值,可以参考RuleConstant.class
,双击shift 搜索RuleConstant,找到RuleConstant.class
例如:
controlBehavior=0 代表为默认的流量控制 (直接拒绝或快速失败)
controlBehavior=1代码WARN UP(预热)
重启order服务
访问服务
查看Sentinel控制台,能看到默认的流控规则了。
设置默认的熔断规则总结:
通用的流控规则在方法上方添加注解@SentinelResource(value = "findById",blockHandler = "orderBlockHandler", fallback = "orderExceptionHandler")
,同时编写对应的降级方法。
RestTemplate的流控规则
修改order服务
@LoadBalanced
@Bean
@SentinelRestTemplate(fallback = "handleFallback", fallbackClass = ExceptionUtil.class, blockHandler="handleBlock",blockHandlerClass=ExceptionUtil.class)
public RestTemplate restTemplate(){
return new RestTemplate();
}
@SentinelRestTemplate说明
-
异常降级
-
fallback:异常降级方法
-
fallbackClass:异常降级类
-
-
限流降级
-
blockHandler:限流降级方法
-
blockHandlerClass:限流降级类
-
添加异常类
package org.example.order.exception;
import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import org.example.order.entity.Product;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
public class ExceptionUtil {
//限流熔断业务逻辑
public static SentinelClientHttpResponse handleBlock(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution, BlockException ex) {
System.err.println("Oops: " + ex.getClass().getCanonicalName());
Product product = new Product();
product.setProductName("限流熔断降级");
return new SentinelClientHttpResponse(JSON.toJSONString(product));
}
//异常熔断业务逻辑
public static SentinelClientHttpResponse handleFallback(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution, BlockException ex) {
System.err.println("fallback: " + ex.getClass().getCanonicalName());
Product product = new Product();
product.setProductName("异常熔断降级");
return new SentinelClientHttpResponse(JSON.toJSONString(product));
}
}
查看sentinel配置,SentinelProperties.class
修改sentinel配置,使得SpringCloud应用启动时,直接与Sentinel建立心跳连接,访问sentinel 控制台就可以看到服务连接情况,不需要第一次访问应用的某个接口时,才连接sentinel。
spring.cloud.sentinel.eager = true
复制Order1Controller得到Order2Controller,修改代码后如下:
package org.example.order.controller;
import org.example.order.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/order2")
public class Order2Controller {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id){
if(id != 1){
throw new RuntimeException("异常的id,抛出异常");
}
return restTemplate.getForObject("http://service-product/product/1", Product.class);
}
}
启动eureka、product、order服务
访问sentinel控制台,直接能看到服务
访问
添加流控规则
测试
访问1次,没有触发流控规则,能正常访问到数据
http://localhost:9002/order2/buy/1
多次访问,触发流控规则,调用了降级方法
控制台显示显示如下
确实是ExceptionUtil的信息
RestTemplate的流控规则总结:
-
RestTemplate的Bean上方添加注解
@SentinelRestTemplate
-
添加异常处理的类
Feign的流控规则
在order-service-feign_hystrix服务修改
在feign中开启sentinel熔断
feign:
# 在feign中开启hystrix熔断
#circuitbreaker:
# enabled: true
# 在feign中开启sentinel熔断
sentinel:
enabled: true
配置FeignClient
和使用Hystrix的方式一致,需要配置FeignClient接口以及通过 fallback 指定熔断降级方法
启动eureka、product、order-service-feign_hystrix服务
测试
浏览器访问
查看sentinel控制台
添加流控规则
访问次数小于阈值,正常返回数据
多次访问超过阈值,触发了Feign熔断的降级方法
Feign的流控规则总结:
-
添加依赖
-
在feign中开启sentinel熔断支持
-
配置FeignClient接口及实现类里定义熔断方法
完成!enjoy it!