SpringCloud-Hystrix断路器
Hystrix介绍
在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很正常的。
Hystrix可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。
Hystrix通过将依赖服务进行资源隔离,进而组织某个依赖服务出现故障的时候,这种故障在整个系统所有的依赖服务调用中进行蔓延,同时Hystrix还提供故障时的fallback降级机制
总而言之,Hystrix通过这些方法帮助我们提升分布式系统的可用性和稳定性。
雪崩问题
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路
假如,我们此时的 微服务I 发生了异常,请求阻塞,用户请求就不会得到响应,则该线程就不会得到释放,于是越来越多的用户请求到来,就会有越来越多的线程阻塞
服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
更直白的说,多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
Hystrix的使用
一、服务熔断
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息,防止系统雪崩,直接熔断整个服务,而不是一直等到此服务超时。所以我们可以针对某个接口中的所有方法总的来做一次熔断处理。检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阀值缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是:@HystrixCommand
。
服务熔断通常在我们的服务提供方进行编写
相关Maven
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
主要关注controller层,有点类似于捕获异常,当我们某个请求抛出异常时,使用@HystrixCommand指定抛出异常后走的另一个备用的方法
@RestController public class UserController { @Autowired private UserService userService; @RequestMapping("/getUsername") @HystrixCommand(fallbackMethod = "hystrixGetUserByName") public User getUserByName(@RequestParam("username") String username){ User user = userService.getUserByName(username); if(user==null){ //这里需要做判断抛出异常,然后熔断接收到异常才会去走备份的服务方法 throw new RuntimeException(); } return user; } //熔断备选方案 public User hystrixGetUserByName(@RequestParam("username") String username){ return new User().setUsername(username + "用户不存在"); } }
application.yml
server: port: 8081 mybatis: type-aliases-package: com.chen.springcloudapi.pojo mapper-locations: classpath:mappers/*.xml spring: application: name: springcloud-provider-user datasource: username: root password: 123456 url: jdbc:mysql://192.168.111.129:3306/test?characterEncoding=UTF-8&useSSL=false driver-class-name: com.mysql.jdbc.Driver #Eureka的配置 eureka: client: service-url: defaultZone: http://Eureka-Server1:7001/eureka/,http://Eureka-Server2:7002/eureka/ #Eureka集群只需要用逗号分隔即可 instance: instance-id: springclou-provider-user-hystrix-8081 #修改在Eureka上的默认描述信息
启动类
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker //开启Hystrix public class SpringcloudProviederUserHystrix8081Application { public static void main(String[] args) { SpringApplication.run(SpringcloudProviederUserHystrix8081Application.class, args); } }
此时当我们的服务提供方的 /getUsername 请求抛出异常时,就会走 hystrixGetUserByName 备用的服务方法
而没有做熔断的方法将会直接抛出异常
因此,为了避免因某个微服务后台出现异常或错误而导致整个应用或网页报错,使用熔断是必要的
二、服务降级
服务降级是指 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理,或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务。
资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机某些重要的服务不可用。所以,一般在高峰期,为了保证核心功能服务的可用性,都要对某些服务降级处理。比如当双11活动时,把交易无关的服务统统降级,如查看蚂蚁深林,查看历史订单等等。
服务降级主要用于什么场景呢?当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,可以将一些 不重要 或 不紧急 的服务或任务进行服务的 延迟使用 或 暂停使用。
降级的方式可以根据业务来,可以延迟服务,比如延迟给用户增加积分,只是放到一个缓存中,等服务平稳之后再执行 ;或者在粒度范围内关闭服务,比如关闭相关文章的推荐。
自动降级分类
1)超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况
2)失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
3)故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
4)限流降级:秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)
服务熔断是应用于我们的服务提供方,服务降级是应用于我们的调用方(消费者)
Feign降级
Feign降级的话要看我们的Feign写在哪里,像我们之前Feign是写在服务提供方API中的,所以我们降级的方法也直接在服务提供方API中编写即可
UserClientServiceFallbackFactory
不难看出,其实就是重写了一下Feign的接口方法
@Component public class UserClientServiceFallbackFactory implements FallbackFactory<UserClientService> { @Override public UserClientService create(Throwable cause) { return new UserClientService() { @Override public String getUser() { return null; } @Override public User getUserByName(String username) { return new User().setUsername(username + "用户不存在,没有对应的信息,客户端提供了降级的信息,这个服务现在已经被关闭"); } }; } }
UserClientService
修改FeignClient,添加fallbackFactory,指向我们的降级类
@Component @FeignClient(value = "SPRINGCLOUD-PROVIDER-USER", fallbackFactory = UserClientServiceFallbackFactory.class) //fallbackFactory指定降级配置类 public interface UserClientService { @RequestMapping("/get") public String getUser(); @RequestMapping("/getUsername") public User getUserByName(@RequestParam("username") String username); }
然后消费者中开启我们的hystrix
application.yml
# 开启降级feign.hystrix
feign:
hystrix:
enabled: true
#springcloud2020版本以后则是使用下面的配置
#feign:
# circuitbreaker:
# enabled: true
然后我们需要在启动类上配置@ComponentScan,将我们服务提供者的API进行扫描,主要是要扫描到我们的降级方法。但是这里有一个问题,就是我们@SpringbootApplication中是包含了 @ComponentScan 注解的,所以这两者不能同时用,如果同时用了,@SpringBootApplication 注解自带的 @ComponentScan 注解就不生效了,这样会导致启动类所在的包,除了被自己加的这个 @ComponentScan 关联的会映射到,原本的反而都映射不到了,所以这时候有几种方案可以选择
方法一:扫描多个类,将目标API和本地包都进行扫描
@ComponentScan(basePackages= {"com.chen.springcloudapi", "com.chen.springcloudconsumeruserfeign"} )
方法二:如果本地包和目标API包名称前缀是一致的话可以直接写成下列方式,这样写就同样可以扫描到目标API和本地包了
@ComponentScan("com.chen")
方法三:使用@ComponentScans注解代替@ComponentScan
@SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = {"com.chen.springcloudapi"}) @ComponentScans({@ComponentScan("com.chen.springcloudapi")}) public class SpringcloudConsumerUserFeignApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudConsumerUserFeignApplication.class, args); } }
配置完成后,启动我们的Eureka、服务提供者、消费者
然后关闭我们手动关闭服务提供者再次访问
服务熔断和服务降级区别
- 服务熔断—>服务端:某个服务超时或异常,引起熔断~,类似于保险丝(自我熔断)
- 服务降级—>客户端:从整体网站请求负载考虑,当某个服务熔断或者关闭后,服务将不再调用,此时在客户端,我们可以准备一个 FallBackFactory ,返回一个默认的值(缺省值)。会导致整体的服务下降,但是好歹能用,比直接挂掉强。
- 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
- 实现方式不太一样,服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。
熔断,降级,限流:
限流:限制并发的请求访问量,超过阈值则拒绝;
降级:服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;
熔断:依赖的下游服务故障触发熔断,避免引发本系统崩溃;系统自动执行和恢复
Dashboard流监控介绍
Hystrix Dashboard是Hystrix的一个组件,提供一个断路器的监控面板,主要用来实时监控Hystrix的各项指标。通过Hystrix Dashboard反馈的实时信息,可以使我们更好的监控服务和集群的状态,帮助我们快速发现系统中存在的问题。
Dashboard编写至消费者
新建springcloud-consumer-hystrix-dashboard模块
Maven依赖
<!--dashboard依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <!--hystrix依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
启动类
@SpringBootApplication @EnableHystrixDashboard //开启Dashboard public class SpringcloudConsumerHystrixDashboardApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudConsumerHystrixDashboardApplication.class, args); } }
application.yml
server:
port: 9001
启动后访问该页面
修改之前所写的熔断器(服务提供者)
想在dashboard里监控某个服务,这个服务本身得先在主启动类上@EnableCircuitBreaker 开启熔断开关,然后注入一个的ServletBean
同时这个服务的控制器里面的接口必须有@HystrixCommand的注解,用来标识要把哪些接口方法展示在dashboard上
启动类(新增一个Servlet)
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启Hystrix
public class SpringcloudProviederUserHystrix8081Application {
public static void main(String[] args) {
SpringApplication.run(SpringcloudProviederUserHystrix8081Application.class, args);
}
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
//访问该页面就是监控页面
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
启动Eureka和该熔断器
此时访问该地址是没有数据的
然后我们访问一下@HystrixCommand注解的接口后,就会产生data数据
然后将其路径放置我们的Dashboard中
即可监控到我们的数据,可以看到,所监控的也是我们@HystrixCommand注解的接口
监控数据说明