一、引言
1. 服务雪崩
在分布式项目中,随着项目规模不断扩张,服务之间调用也变得越来越复杂,链路也变得越来越长,假设各服务之间的调用关系如下图:
当服务C因为请求流量过大响应过慢或自身出现问题时,若服务B仍不断调用服务C,那么自身的资源也将会被一点点耗尽,从而导致服务B也变为不可用,进而导致服务A也无法正常使用。 因为一个服务失败,从而导致整个链路的服务都失败,这种现象被称为服务雪崩。 为了避免服务雪崩,通常我们会采用服务降级与服务熔断这两种方式来解决。
2. 服务降级
那么,什么是服务降级呢? 简单来说就是在请求响应过慢或下游系统不可用时,主动调用一些降级逻辑,迅速返回,避免长时间的等待。
##3. 服务熔断
其实服务熔断也是一种服务降级的策略,与服务降级不同是,服务熔断会直接放弃调用响应过慢或不可用的下游系统,从而保证自身可用性,等到目标系统好转时重新恢复调用。
这套熔断机制的常用的是断路器模式:
- 一开始处于
Closed状态,当错达到阈值时,转化为Open - 当超过设置的
reset timeout时,状态会转变为Half Open,此时会尝试调用,一旦成功,便会重新转为Closed。
二、Hytrix
1. Hytrix简介
Hystrix可通过添加延迟公差和容错逻辑来控制分布式服务之间的交互。 通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的。 需要注意的一点是,Hytrix已经进入了维护模式。
2. Hytrix的作用
- 提供依赖项的延迟和失败的控制保护。
- 防止分布式系统的级联故障。
- 快速失败和恢复。
- 回退并在可能的情况下降级。
- 近乎实时的监视,警报和操作控制。
3. Hytrix设计原则
- 防止任何单个依赖项耗尽所用户线程。
- 减少负载并快速失败,而不是排队。
- 在可行的情况下提供备用,以保护用户免受故障的影响。
- 使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一种依赖关系的影响。
- 通过近实时指标,监控和警报确保故障及时发现。
- 以低延迟传播配置更改优化恢复时间,并支持动态属性更改进行实时操作修改。
- 防止整个依赖客户端执行失败,而不仅仅是网络通信失败。
4. Hytrix实现原理
- 通过命令模式将外部调用包装在
HystrixCommand或HystrixObservableCommand对象中,并在单独线程中执行。 - 为每个依赖项维护一个小的线程池(或信号灯),如果已满,发往该依赖项的请求将立即被拒绝,而不是排队。
- 记录失败(客户端抛出的异常),超时和线程拒绝。
- 设置失败阈值,在超过该阈值时打开短路器,拒绝请求。
- 请求失败,被拒绝,超时或短路时执行回退逻辑。
- 实时监控指标和配置更改。
5. 流程图
Hystrix的路程图如下:
具体执行流程:
- 构造
HystrixCommand或HystrixObservableCommand对象,前者为阻塞的,后者为非阻塞; - 执行
HystrixCommand的queue().get()方法,或HystrixObservableCommand的toObserve()方法获取响应,其实toObserve()是执行其父类的方法; - 如果请求启用了缓存且请求响应在缓存中,则返回缓存中的响应;
- 检查断路器是否开启,若开启则调到8,否则进入5;
- 检查线程池和队列是否已满,若满了则走到8;
- 执行
run()或construct()方法,返回单个响应或Observable; - 记录统计信息,根据这些信息来判断何时开启断路器;
- 执行配置的fallback方法;
- 返回。
3. Hystrix例子
本文的例子是Spring Boot + Spring Cloud Feign + Spring Cloud Eureka。
3.1 创建服务注册中心,
- 引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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>
- 创建
applicaiton.yml配置文件
server:
port: 7001
spring:
application:
name: cloud-eureka-server
eureka:
instance:
appname: 127.0.0.1
client:
register-with-eureka: false # false:
fetch-registry: false # false:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
- 创建启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain {
public static void main(String[] args) {
SpringApplication.run(EurekaMain.class, args);
}
}
3.2 创建服务提供者
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
- 创建配置文件
server:
port: 8001
eureka:
instance:
instance-id: payment8001
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://127.0.0.1:7001/eureka,
- 创建service和controller,本文只是为了学习Hystrix,做个简单的例子,不涉及数据库操作。 controller层
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Resource
private IPaymentService paymentService;
@RequestMapping("/success")
public String successMethod() {
return paymentService.successMethod();
}
@RequestMapping("/fail")
public String getTimeout(@RequestParam("id")Integer id){
return paymentService.failMethod(id);
}
}
service层
public interface IPaymentService {
String failMethod(Integer id);
}
@Service
public class PaymentServiceImpl implements IPaymentService{
@Override
@HystrixCommand(fallbackMethod="fallBackMethod", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
})
public String failMethod(Integer id) {
if (id < 0) {
throw new RuntimeException("=====id不能为负数=====");
}
return "fail";
}
public String fallBackMethod(Integer id) {
return "fallBackMethod:" + id;
}
}
circuitBreaker.enabled:启用熔断器;
circuitBreaker.requestVolumeThreshold:请求次数阈值,小于该阈值时,即便其他条件满足也不会开启断路器;
circuitBreaker.sleepWindowInMilliseconds:断路器打开后,重新尝试等待的时间;
circuitBreaker.errorThresholdPercentage:错误次数的百分比。
更详细的配置可以在HystrixCommandProperties类中查看。
4. 创建消费者
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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>
- 创建配置文件
server:
port: 80
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://127.0.0.1:7001/eureka
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
logging:
level:
com.fegin.service.IPaymentFeign: debug
# 开启hystrix
feign:
hystrix:
enabled: true
创建启动类
@SpringBootApplication
// 开启Feign
@EnableFeignClients
// 开启Hystrix
@EnableCircuitBreaker
public class Applicaiton {
public static void main(String[] args) {
SpringApplication.run(Applicaiton.class, args);
}
}
创建controller,并配置服务降级,这种方式可以配置当前类的全局降级方法。
@RestController
@RequestMapping("/cons")
@DefaultProperties(defaultFallback = "global_fialMethod")
public class ConsumerController {
@Resource
IPaymentFeign paymentFeign;
@GetMapping("/fail")
@HystrixCommand
public String get(){
return paymentFeign.failMethod();
}
public String global_fialMethod() {
return "global_fialMethod";
}
}
另一种配置,因为整合了OpenFeign,而OpenFeign自己支持Hystrix,可以直接实现接口,当服务调用失败是,会走本地实现类的方法,让编码更优雅。
@Component
@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX",fallback = PaymentImpl.class)
public interface IPaymentFeign {
@RequestMapping("/payment/failMethod")
String failMethod();
}
@Service
public class PaymentImpl implements IPaymentFeign{
@Override
public String failMethod() {
return "local failMethod";
}
}
本文是本人学习中的总结与笔记,如有不足,希望各路大神能够支出。