在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
Hystrix 的异常处理中,有5种出错的情况下会被 fallback 所截获,从而触发 fallback,这些情况是:
FAILURE:执行失败,抛出异常。
TIMEOUT:执行超时。
SHORT_CIRCUITED:断路器打开。
THREAD_POOL_REJECTED:线程池拒绝。
SEMAPHORE_REJECTED:信号量拒绝。
服务熔断:
当某个服务单元发生故障之后,通过断路器的故障监控,某个异常条件被触发,直接熔断整个服务。向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出吊牌用方法无法处理的异常,就保证了服务调用方的线程不会被长时间占用,避免故障在分布式系统中蔓延,乃至雪崩。服务熔断是配置于服务端的防范机制。
我们创建一个带有熔断机制的provider服务:
所需依赖:
<!-- openfeign -->
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
配置文件:
server:
port: 8003
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: cn.kgc.zhx.pojo
spring:
application:
name: springcloud-provider001
datasource:
url: jdbc:mysql://127.0.0.1:3306/springcloudtest?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimeZone=UTC
driver-class-name: org.gjt.mm.mysql.Driver
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
#=================================eureka===================================
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/,http://127.0.0.1:7002/eureka/
info:
name: ZhangHaoxiang's Provider-8003
service: About Dept
添加主启动类注解@EnableHystrix:
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix//开启熔断机制
public class ProviderHystrixMain8003 {
public static void main(String[] args) {
SpringApplication.run(ProviderHystrixMain8003.class,args);
}
}
provider服务的dao层和service层与熔断无关,我就不再演示。
编写controller层,核心注解 @HystrixCommand:
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
//正常的功能方法
@GetMapping("/dept/select/{id}")
//这个注解的作用就是,在触发熔断机制时,去找备选响应
@HystrixCommand(fallbackMethod = "hystrixSelect")
public CommonResult selectById(@PathVariable int id){
CommonResult<Dept> commonResult=new CommonResult<Dept>();
Dept dept = deptService.selectById(id);
if (dept!=null){
commonResult.setData(dept);
commonResult.setMessage("8003查询成功!");
commonResult.setCode(200);
return commonResult;
}else {
//通过抛出异常触发熔断机制
throw new RuntimeException("没有相关信息,dept==null");
}
}
//备选响应
public CommonResult hystrixSelect(@PathVariable int id){
return new CommonResult(444,"hystrix返回:没有查询到相关信息");
}
运行服务并测试:
首先我们查询一个数据库存在的dept
然后查询一个数据库不存在的dept
可以看出这里触发熔断机制,调用了备选服务。
服务降级:
服务降级处理是在客户端实现完成的,与服务端没有关系,整体资源快不够了,忍痛将某些服务单元先关掉,关闭后还要返回一些可处理的备选方法,待渡过难关,再开启回来。
我们创建一个带有服务降级机制的consumer客户端:
所需依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<!--hystrix(openfeign整合了hystrix,所以这个依赖可以不添加) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
配置文件:
server:
port: 8080
#======================eureka========================
eureka:
client:
register-with-eureka: false
service-url:
#注册中心地址
defaultZone: http://127.0.0.1:7001/eureka/,http://127.0.0.1:7002/eureka/
#====================hystrix============================
feign:
hystrix:
#开启服务降级
enabled: true
主启动类添加@EnableFeignClients注解。
@SpringBootApplication
@EnableFeignClients //开启Feign
public class ConsumerFeignMain {
public static void main(String[] args) {
SpringApplication.run(ConsumerFeignMain.class,args);
}
}
然后我们需要编写一个服务降级时调用的fallback服务工厂。
这里的实现需要基于feign,不了解feign的可以看一下我之前的博客
这里需要继承FallbackFactory接口,并返回我们所需要降级的整个服务的备选方案。也就是DeptFeignService的实现。当客户端在注册中心调用此服务,发现服务已经被关闭时,将会执行此备选服务。
@Component
public class DeptFallBackFactory implements FallbackFactory {
@Override
public Object create(Throwable throwable) {
return new DeptFeignService() {
@Override
public CommonResult selectById(int id) {
return new CommonResult(444,"服务已关闭,触发服务降级");
}
@Override
public CommonResult add(Dept dept) {
return new CommonResult(444,"服务已关闭,触发服务降级");
}
};
}
}
编写完成后,进行测试。
provider服务开启的情况下:
关闭provider服务:
触发服务降级。
有很多小伙伴弄不懂为啥说服务熔断是作用于服务端,服务降级是作用于客户端。其实很好理解。服务熔断的触发条件是客户端的请求已经到达了provider服务,但provider服务端由于某种原因触发了熔断机制,执行备选服务,返回给客户端一个结果,所以说熔断机制是在服务端触发的。但服务降级是服务端已经完全关闭服务,不可能再执行任何操作,由于客户端根本找不到服务端提供的服务,自己给自己生成的一套备选返回方案,所以是在客户端本身触发的。