在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应
服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程
如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了
Hystrix是NetFlex公司提供的一套用于分布式系统的延迟和容错的开源库。在分布式系统里,许多依赖不可避免的调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性
Hystrix的作用:服务降级、服务熔断、服务限流
一般来讲:先配置服务限流,然后再配置服务降级,进而服务熔断
目的:都是为了保证在高并发的情况,系统永远是可用的
Closed
OPEN
Half Open
<!-- 导入熔断器的启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class OrdersApplication {
public static void main(String[] args) {
SpringApplication.run(OrdersApplication.class,args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
@RestController
@RequestMapping("/orders")
public class OrdersController {
//如果该方法或服务提供者发生异常,那么会执行熔断降级方法
@HystrixCommand(defaultFallback = "errorResult")
@GetMapping("/findAllProduct")
public ResultVO findAllProduct(Integer num){
System.out.println(1 / num);
ResultVO resultVO = restTemplate.getForObject("http://star-product/product/findAllProduct",ResultVO.class);
return resultVO;
}
//熔断降级方法
public ResultVO errorResult(){
return ResultVO.fail("降级处理!");
}
}
项目中需要导入feign依赖,以及在启动类上开启feign,不需要额外导入Hystrix依赖,因为OpenFeign自带
feign:
hystrix:
enabled: true
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableCircuitBreaker
public class OrdersApplication {
public static void main(String[] args) {
SpringApplication.run(OrdersApplication.class,args);
}
}
//fallback声明了用于降级处理类
@FeignClient(value = "star-product",fallback = ProductServerFeignImpl.class)
public interface ProductServerFeign {
@GetMapping("/product/findAllByNum/{num}")
ResultVO findAllByNum(@PathVariable("num") Integer num);
}
该类中重写的方法,即为对应feign接口中方法在运行出现熔断降级时,执行的方法
//需要被spring容器管理
@Component
public class ProductServerFeignImpl implements ProductServerFeign {
public ResultVO findAllByNum(Integer num) {
return ResultVO.fail("触发降级处理!");
}
}
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
//如果参数为1,则引发异常;如果参数为2,则引发超时
@GetMapping("/findAllByNum/{num}")
public ResultVO findAllByNum(@PathVariable("num") Integer num) throws InterruptedException {
if(num == 1){
System.out.println(1/0);
}
if(num == 2){
Thread.sleep(2000);
}
List<Product> list = productService.findAllProduct();
return ResultVO.success("查询所有商品成功",list);
}
}
避坑1:此时的timeoutInMilliseconds需要大于ribbon的默认读取时间(1秒)和默认连接时间(1秒),否则,ribbon没有来得及重试,就会熔断
避坑2:如果程序的读取速度过慢,例如,自己手动设置线程睡眠超过1秒,就需要修改ribbon的默认读取时间
hystrix:
command:
default:
circuitBreaker:
requestVolumeThreshold: 20 #10S范围内,熔断器至少接收20个请求,才会执行熔断判断逻辑,默认20个
sleepWindowInMilliseconds: 5000 #熔断以后,5S内,熔断器的状态都出于半开状态,默认5秒
errorThresholdPercentage: 50 #10S范围内,超过50%的请求出问题,服务开始熔断,默认50%
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 #超过1秒钟,微服务调用将超时,默认1秒
@HystrixCommand(fallbackMethod="fallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" )
}
)
针对服务级别的话,直接配置service-id,如下:
hystrix:
command:
service-id:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
server:
port: 7070
spring:
application:
name: hystrix-dashBoard
@SpringBootApplication
@EnableHystrixDashboard
public class DashBoardApplication {
public static void main(String[] args) {
SpringApplication.run(DashBoardApplication.class,args);
}
}
如果是OpenFeign和Hystrix整合,没有加入Hystrix依赖的话,那么需要在该微服务上,添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
并且在该项目的启动类上,添加@EnableCircuitBreaker
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
/**
* 此配置是为了服务监控而配置,与服务器容器本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
* 只要在自己的项目里配置下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
例如:http://localhost:8090/hystrix.stream
可以查看出,在被监控微服务调用时,在向dashboard发送消息
编号 | 解释 |
---|---|
1 | 圆点:微服务的健康状态,有绿色,黄色,橙色,红色,健康状态依次降低 |
2 | 线条:流量变化 |
3 | 请求的方法 |
4 | 成功请求(绿色) |
5 | 短路请求(蓝色) |
6 | 坏请求(青色) |
7 | 超时请求(黄色) |
8 | 被拒绝的请求(紫色) |
9 | 失败请求(红色) |
10 | 最近10秒钟内请求错误的百分比 |
11 | 请求频率 |
12 | 熔断器状态 |
13 | 数据延迟统计 |
14 | 线程池 |
客户端弹性模式的重点是,当远程服务表现不佳或发生错误的时候,保护调用该远程服务的客户端,让该客户端能够“快速失败”,而不消耗诸如数据库连接和线程池之类的宝贵资源,从而避免客户端崩溃。常见的客户端弹性模式有:隔离、超时、限流、熔断、降级。
https://github.com/alibaba/Sentinel/releases
把sentinel拷贝项目同级目录,在cmd中运行sentinel项目
账号密码默认为sentinel
java -Dserver.port=9090 -Dcsp.sentinel.dashboard.server=localhost:9090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
cloud:
sentinel:
transport:
port: 9999 # 与控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:9090 # 指定控制台服务的地址
通过localhost:9090访问sentinel控制台
需要访问上游服务的控制层方法,才可以在sentinel控制台中显示
可以控制指定资源路径的每秒访问数
可以控制指定资源路径的并发线程数
直接:对接口的访问达到限流条件时,开启限流。(默认值)
关联:当关联的资源达到限流条件时,开启限流。
链路:当从某个接口过来的资源达到限流条件时,开启限流。
对于同一service资源,被控制层的两个方法调用时,可以通过在该service方法上添加@SentinelResource("资源名称")的注解,并且,在簇点链路中设置该资源流控为链路,来控制该资源限流
链路流控时需要添加配置和代码
spring:
application:
name: star-orders
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yml
sentinel:
transport:
port: 9999 # 与控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8080 # 指定控制台服务的地址
filter:
enabled: false # 关闭sentinel的CommonFilter实例化
@Configuration
public class RootConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
// 入口资源关闭聚合
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}
访问超时,会进行降级
在一定时间内,异常数量到达阈值则进行降级
热点规则可以对控制层的参数进行流量控制,如果该参数达到阈值,就会进行熔断
在需要添加热点规则的控制层方法上添加注解添加@SentinelResource("资源名称")
对该资源进行添加热点规则
在热点控制规则生成后,进行编辑,编辑例外的参数值
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String comefrom = httpServletRequest.getParameter("comefrom");
return comefrom;
}
}
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT(平均响应时间)、入口 QPS 、CPU使用率和线程数五个维度监控应用数据,让系统尽可能保持在最大吞吐量的情况下,还能保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。
Load(仅对 Linux/Unix-like 机器生效):当系统 load1 (load1指1分钟之内的平均负责)超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。
以下的类,会自动拦截所有Sentinel为了保护资源而抛出的异常,我们可以根据异常的具体类型,来向客户端响应具体的信息
@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
response.setContentType("application/json;charset=utf-8");
ResultVO r = null;
//BlockException 异常接口,包含Sentinel的五个异常
//1、 FlowException 限流异常
//2、 DegradeException 降级异常
//3、 ParamFlowException 参数限流异常
//4、 AuthorityException 授权异常
//5、 SystemBlockException 系统负载异常
if (e instanceof FlowException) {
r = new ResultVO(-1, "接口被限流了...");
} else if (e instanceof DegradeException) {
r = new ResultVO(-2, "接口被降级了...");
}
response.getWriter().write(JSON.toJSONString(r));
}
}
对于使用注解@SentinelResource声明的资源,可以使用自定义方法进行处理
//blockHandler声明的方法需要有参数为BlockException,用于接收抛出的规则的异常
//fallbackHandler,用于其他异常
@SentinelResource(value = "test",blockHandler = "paramFlowHadler",fallback = "fallbackHandler")
sentinel中每次重启服务时,对应服务的规则都需要重新配置,一旦项目进入生产环境,规则变动不大,而且配置会有很多,这时就要使用规则持久化。
public class FilePersistence implements InitFunc {
@Override
public void init() throws Exception {
// TIPS: 如果你对这个路径不喜欢,可修改为你喜欢的路径
String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
System.out.println(ruleDir);
String flowRulePath = ruleDir + "/flow-rule.json";
String degradeRulePath = ruleDir + "/degrade-rule.json";
String systemRulePath = ruleDir + "/system-rule.json";
String authorityRulePath = ruleDir + "/authority-rule.json";
String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
this.mkdirIfNotExits(ruleDir);
this.createFileIfNotExits(flowRulePath);
this.createFileIfNotExits(degradeRulePath);
this.createFileIfNotExits(systemRulePath);
this.createFileIfNotExits(authorityRulePath);
this.createFileIfNotExits(paramFlowRulePath);
// 流控规则
ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
flowRuleListParser
);
// 将可读数据源注册至FlowRuleManager
// 这样当规则文件发生变化时,就会更新规则到内存
FlowRuleManager.register2Property(flowRuleRDS.getProperty());
WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
// 将可写数据源注册至transport模块的WritableDataSourceRegistry中
// 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
// 降级规则
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath,
degradeRuleListParser
);
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
degradeRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
// 系统规则
ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
systemRulePath,
systemRuleListParser
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
// 授权规则
ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
authorityRulePath,
authorityRuleListParser
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
// 热点参数规则
ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
paramFlowRulePath,
paramFlowRuleListParser
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
}
private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<FlowRule>>() {
}
);
private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<DegradeRule>>() {
}
);
private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<SystemRule>>() {
}
);
private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<AuthorityRule>>() {
}
);
private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<ParamFlowRule>>() {
}
);
private void mkdirIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
}
private void createFileIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}
在resouces下创建META-INF/services目录
并在该目录下新建文件,文件名为com.alibaba.csp.sentinel.init.InitFunc,
文件内容为配置类的全类名top.ygang.config.FilePersistence
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
feign:
sentinel:
enabled: true
有两种,分别fallback和fallbackFactory,两种都可以实现
//自定义类实现feign接口
@Service
public class ProductFallback implements IProductService {
@Override
public ResultVO findAllProduct() {
return ResultVO.success("使用ProductFallback的备用方法");
}
}
对应的feign接口中需要添加注解信息
@FeignClient(
value = "star-product",
fallback = ProductFallback.class
)
//自定义类实现FallbackFactory接口,泛型为feign接口
@Service
public class ProductFallbackFactory implements FallbackFactory<IProductService> {
@Override
public IProductService create(Throwable throwable) {
System.out.println("ProductFallbackFactory.create:"+throwable);
return new IProductService() {
@Override
public ResultVO findAllProduct() {
return ResultVO.success("这是ProductFallbackFactory中的备用方法!!");
}
};
}
}
对应的feign接口中需要添加注解信息
@FeignClient(
value = "star-product",
fallbackFactory = ProductFallbackFactory.class
)