分布式系统中服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务会出现故障,导致依赖于他们的其他服务出现远程调度的线程阻塞。某个服务的单个点的请求故障会导致用户的请求处于阻塞状态,最终的结果是整个服务的线程资源消耗殆尽。由于服务的依赖性,会导致依赖于该故障服务的其他服务也处于线程阻塞状态,最终导致这些服务的线程资源消耗殆尽,知道不可用,从而导致整个服务系统不可用,即雪崩效应。为了防止雪崩效应,产生了熔断器模型。
Hystrix是Netflix公司开源的一个项目,提供了熔断器功能,能阻止分布式系统中出现联动故障。Hystrix是通过隔离服务的访问点阻止联动故障的,并提供了故障解决方案,从而提高了整个分布式系统的弹性。
当服务的某个API接口的失败次数在一定时间内小于设定的阈值时,熔断器处于关闭状态,该API接口正常提供服务。当该API接口处理请求的失败次数大于设定的阈值时,hystrix判定该API接口出现了故障,打开熔断器,这时请求该API接口会执行快速失败的逻辑(即fallback回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态。处于打开状态的熔断器,一段时间后会处于半打开状态,并将一定数量的请求执行正常逻辑,剩余的请求会执行快速失败,若执行正常逻辑的请求失败了,则熔断器继续打开,若成功了,则关闭熔断器。这样熔断器就具有了自我修复的能力。
最开始集成的时候,发现springBoot1.5.6 和springBoot2.0.3包不兼容,导致后面一致报错,因为工作环境用的还是srpingBoot1.5.6版本,所以练习的这个是最新版本,Hystrix在1.5版本是要自己加载的,但是2.0.3版本已经包含在spring-cloud-starter-netflix-hystrix 包里面了,我是用maven仓库,工作空间都在一起,后面分离了工作空间和maven仓库,让两个版本的依赖仓库分开 ,果然之后一切顺利,这也是比较坑的地方,
先写三个项目springcloud-eureka-server 服务注册中心 springcloud-euraka-client生产者 euraka-consumer-ribbonAndfeign-hystrix 消费者调用 ,并且ribbon 和 feign负载均衡放在一个项目里
主要是euraka-consumer-ribbonAndfeign-hystrix 项目, 先贴出POM.XML的依赖
在项目启动类加上@EnableHystrix注解,开启hystrix熔断器功能,当然我这里加的比较多,如果是springBootCloudApplicateion也可以,主要是在超时配置的时候,会有过服务重试的操作,这个操作研究了一段时间,基本上就是超时的策略,会去调用其它服务实例,再次请求服务等,详细介绍在配置文件里
@SpringBootApplication
@Configuration
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class DemoServerApplication {
public static void main(String[] args) {
SpringApplication.run(DemoServerApplication.class, args);
}
/* SpringCloud重试机制配置
首先声明一点,这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。
其实也是Ribbon+RestTemplate的重试机制,因为是SpringCloud默认方式
对于整合了Ribbon的RestTemplate,例如一个RestTemplate添加了@LoadBalanced 注解
*/
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
/*HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setReadTimeout(3000);
httpRequestFactory.setConnectTimeout(2000);
return new RestTemplate(httpRequestFactory);*/
return new RestTemplate();
}
//feign重试机制,feign默认是通过自己包下的Retryer进行重试配置,默认是5次,优先配置文件配置的重试策略
//如果没有配置文件,下面这样设置至少也会重试两次,本实例之外的一次调用
@Bean
public Retryer feignRetryer(){
// return new Retryer.Default(100,TimeUnit.SECONDS.toMillis(1),5);//默认是5次
return Retryer.NEVER_RETRY; //feign取消重试
}
然后在CustomerRibbonController里,实现Ribbon+RestTemplate 负载均衡实现 ,其实也比较简单,因为我之前测试别的原因,我写了两个方法
@RestController
public class CustomerRibbonController {
@Autowired
RestTemplate restTemplate;
@Autowired
RibbonService ribbonService;
//这里写了一个service 层来表示业务逻辑,当然与下面worldCustomer的方式调用也是可以的,简洁一些
// hellolmc 方法调用超时之后, 调用fallbackMethod = "hiError" 方法,自动熔断或降级
@GetMapping("/hilmc")
public String hellolmc(){
return ribbonService.consumer();
}
//此方法可以正常调用,生产者并没有超时
@RequestMapping(value = "/helloCustomer",method = RequestMethod.GET)
@HystrixCommand(fallbackMethod="fallback")
public String worldCustomer(){
return restTemplate.getForObject("http://springcloud-euraka-client/world", String.class);
}
public String fallback() {
System.out.println("fallbackMethod worldCustomer");
return "fallback Customer";
}
}
@Service
public class RibbonService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "hiError")
public String consumer() {
return restTemplate.getForObject("http://springcloud-euraka-client/hello", String.class);
}
public String hiError() {
return "fallbackCall";
}
}
RibbonService 是因为把业务把控制层分开,第一个方法是这样调用
在consumer()方法上加上@HystrixCommand注解。有了该注解consumer()方法就启用了Hystrix熔断器的功能,fallbackMethod为处理回退(fallback)逻辑的方法为hiError() ,没有异常,直接返回一个字符串,也可以用抛异常的方式执行,这样在日志里可以发现哪个服务出现问题,只需要在 hiError() 里加thrable 等方式,我这里为了简便就没有做,主要是不想处理复杂的逻辑,,这样方便执行快速失败,释放线程资源,项目结构是这样
加上配置文件,因为是ribbon 和 feign放在一起,还有重试策略配置等,
server:
port: 5080
eureka:
client:
serviceUrl:
defaultZone: http://peer1:5012/eureka/
healthcheck:
enabled: true
lease:
duration: 5
instance:
preferIpAddress: true
spring:
application:
name: springCloudEurakaConsumer
#使用如下配置开启,即可实现重试:再加Feign下面的ribbon配置,就是 Ribbon+RestTemplate的重试机制
# cloud:
# loadbalancer:
# retry:
# enabled: true
#开启hystrix功能
feign:
hystrix:
enabled: true
#logging:
# level:
# org.exampledriven.eureka.customer.shared.CustomerServiceFeignClient: FULL
#lease 租期时间,不定时发送心跳包消息,如果长时间未能更新租期时间,服务就为抛弃该服务实例,注册中心会当成无用服务
# 1,Ribbon+RestTemplate的重试
#对于整合了Ribbon的RestTemplate,例如一个RestTemplate添加了@LoadBalanced 注解
#在此基础上,使用如下配置,即可实现重试:
#Feign的重试与Ribbon重试整合后, 只需使用Ribbon的重试配置即可,两种重试配置一样,所以下面配置至少要重试4次
ribbon:
# 同一实例最大重试次数,不包括首次调用,配置后本实例至少会执行两次
MaxAutoRetries: 1
# 重试其他实例的最大重试次数,不包括首次所选的server,配置后本实例和其它服务注册实例至少会执行两次
MaxAutoRetriesNextServer: 1
# 是否所有操作都进行重试
OkToRetryOnAllOperations: false
到这里ribbon配置基本完成,
依次启动springcloud-eureka-server 的服务注册,springcloud-euraka-client生产者 euraka-consumer-ribbonAndfeign-hystrix消费消息,在浏览器访问http://localhost:5080/hilmc,会显示如下, 因为我在生产端直接睡眠了9秒,所以触发熔断
再输入正常url http://localhost:5080/helloCustomer 显示正常
在Feign上使用熔断器 下面是feign 包
Feign的起步依赖中已经引入了Hystrix的依赖,只需要在eureka-feign-client工程的配置文件中开启hystrix功能即可,前方配置文件已经给出
EurekaClientFeign 接口,
@Component
@FeignClient(value = "springcloud-euraka-client",fallback = HiHystrix.class)
public interface EurekaClientFeign {
/**
* 绑定 springcloud-euraka-client 服务的 /hi/ 接口
*
* @param name
* @return
*/
@GetMapping(value = "/hi")
String sayHiFromClientEureka(@RequestParam(value = "name")String name);
@GetMapping(value = "/hi2")
String sayHiFromClientEureka2(@RequestParam(value = "name")String name);
}
HiHystrix 类必须实现EurekaClientFeign接口,才能触发熔断器的fallback
@Component
public class HiHystrix implements EurekaClientFeign {
@Override
public String sayHiFromClientEureka(String name) {
return "hi,"+name+",sorry.error!";
}
@Override
public String sayHiFromClientEureka2(String name) {
return "hi2,"+name+",sorry.error! Hi2";
}
}
CustomerFeignController 类
@RestController
public class CustomerFeignController {
@Autowired
EurekaClientFeign eurekaclient;
//超时调用fallback 的方法,如果禁止重试,也会调用两次,如果配置文件有调用其它实例,至少会执行3次以上
//所以在写接口的时候,需要对接口增加幂等性
@GetMapping("/hi")
public String sayHi(@RequestParam(defaultValue = "cralor",required = false)String name){
return eurekaclient.sayHiFromClientEureka(name);
}
//正常执行调用
@GetMapping("/hi2")
public String sayHi2(@RequestParam(defaultValue = "lmc",required = false)String name){
return eurekaclient.sayHiFromClientEureka2(name);
}
@Autowired
private GreetingClient greetingClient;
@RequestMapping("/get-greeting")
public String greeting(Model model) {
Map
parpm.put("name", "lmc");
parpm.put("test", "demo");
String message = greetingClient.greeting(parpm.toString()); //以字符串方式传参数
return message+":传参数";
}
浏览器访问输入http://localhost:5080//hi2 正常调用
浏览器访问输入http://localhost:5080//hi
这个方法在springcloud-euraka-client 项目里启动睡眠6秒超时
都 能触发熔断降级处理, 说明是可行,
还要注意的是 feign 重试机制,就是在超时或者异常的情况下,会不断去重试调用,feign默认重试次数是5次,100ms一次
这最前面的配置文件最后参数有说明 ,就是除了本身实例,本身服务,再去调用其它实例和服务,在集群的环境下配置这种策略最好,如果碰上数据量过大请求过去超时,但是生产者那方会如果没有限制超时就会继续执行,这个时间再去重试请求其实是非常危险的,一条数据会碰上执行两次以上,get的数据可能影响不大,如果是post写数据就要考虑接口的幂等性
使用Hystrix Dashboard监控熔断器的状态 这个跟eurak服务注册类型的步骤,有时间再弄会,集群监控所有熔断器的状态和运用情况等
githut项目地址 https://github.com/lmchuyang/huyang
如有理解 错的,希望大家能指正交流
参考资料 https://www.cnblogs.com/cralor/p/9230728.html
参考资料 https://www.jb51.net/article/129336.htm