1. 背景
在微服务分布式环境下,服务被我们拆分成了许多服务单元,服务之间通过注册和订阅机制相互依赖。系统间的依赖十分的庞大和复杂,一个请求可能会经过多个依赖服务,最后完成调用。
分布式应用中存在错综复杂的相互依赖。
1.1 微服务面临的问题
当系统中某个服务出现延迟或者不可用时,那么整个用户请求都被阻塞,最终导致该用户功能不可用。依赖的服务越多,那么不可用的风险就越大。
高请求量情况下,由于网络原因或者是服务自身的不可用,导致出现故障或者延时。这些问题会导致服务调用方对外提供的服务出现延迟,此时调用方的外来请求不断增加,任务不断的积压,资源不断被占用,最后导致服务调用方自身服务瘫痪。
高并发情况下如果其中一个服务不可用,那么整个系统都可能会面临崩溃的风险。对比传统高可用,传统高可用相互独立,互不影响。
举个例子,在我们的模拟交易系统中,我们会将我们的系统拆分成学生、交易、股票、教学等一系列模块。学生端发出交易请求到交易中心,交易中心又会发请求到股票服务查询一些股票的基本信息。这个时候如果股票服务由于自身或者网络原因出现延迟,那么交易中心的请求就会阻塞等待股票服务的返回。漫长的等待之后,股票服务调用失败,返回信息给交易中心,交易中心又将失败结果返回给学生端。在高并发的情况下,这些阻塞的线程就会导致交易中心资源被大量占用,最终导致交易中心不可用。
1.2 断路器
在我们的家里,都会安装断路器或者是保险丝保护电路过载。当电流过大时自动跳闸或者熔断,避免设备损坏甚至火灾等严重后果。
那么在我们微服务体系中有没有这么一个“断路器”,当服务不可用时及时切断调用链路,快速响应失败,来保护我们服务的安全呢?
2.1 Hystrix简介
对于以上的问题,Spring Clould Hystrix实现了断路器、依赖隔离、监控等一系列功能。Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix/test/advisor主要通过以下几点实现可用性与容错性。
- 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这里使用了设计模式中的“命令模式”。
- 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时和被拒绝的请求等。
- 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
2.2 如何使用
2.2.1 单独使用
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 编码
@RestController
@RequestMapping("/stock")
public class StockController {
@Autowired
private StockService stockService;
@GetMapping()
public Stock getStock() {
return StockService.getStock();
}
}
@Service
public class StockService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getStockFallback")
public Stock getStock() {
// 如果调用多次调用失败,直接触发熔断,调用getStockFallback()
return restTemplate.getForObeject("http://STOCK-SERVICE/stock", Stock.class);
}
@GetMapping()
public Stock getStockFallback() {
// 降级逻辑不依赖网络等其他有风险的渠道
return new Stock();
}
}
- 详细配置参考
spring-cloud-netflix-hystrix-x.x.x.x.jar/MATA-INF/spring-configuration-metadata.json
2.2.2 结合Feign使用
- 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 编码
@RestController
@RequestMapping("/stock")
public class StockController {
@Autowired
private RestTemplate restTemplate;
@GetMapping()
public Stock getStock() {
return StockService.getStock();
}
}
@FeignClient(value = "STOCK-SERVICE", fallbackFactory = StockServiceFallback.class)
@RequestMapping("/stock")
public class StockService {
@GetMapping()
public Stock getStock();
}
@Component
public class StockServiceFallback implements StockService {
public Stock getStock() {
return new Stock();
}
}
- 详细配置参考
spring-cloud-openfeign-core-x.x.x.x.jar/MATA-INF/spring-configuration-metadata.json
3 原理分析
3.1 Hystrix在整个体系中的位置
3.2 集成Feign源码分析
在Feign源码分析中我们知道了,@EnableFeignClients
注解中导入了FeignClientsRegistrar.class
,registerBeanDefinitions()
为入口函数,我们从registerBeanDefinitions()
函数开始分析。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// 扫描到所有@FeignClient注解
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 省略...
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 省略...
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
// 解析@FeignClient,生产FeignClient工厂
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 省略...
// 定义FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
// 解析@FeignClient,读取将其中的属性配置到FeignClientFactoryBean
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
// 省略...
// 注册
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
FeignClientsRegistrar
中我们注册了FeignClientFactoryBean
,在分析这个类之前我们先看一下配置类。
@Configuration
public class FeignClientsConfiguration {
@Configuration
@ConditionalOnClass({
HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({
FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
看完配置之后我们分析一下FeignClientFactoryBean
这个类。
FeignClientFactoryBean
实现了FactoryBean
接口。实现了FactoryBean
接口代表他是一个工厂类,最终会通过getObject()
方法返回真正的类。关于FactoryBean
的知识大家可以自行去扩展学习。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
@Override