博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
服务容错保护断路器Hystrix之一:入门示例介绍(springcloud引入Hystrix的两种方式)...
阅读量:7203 次
发布时间:2019-06-29

本文共 12452 字,大约阅读时间需要 41 分钟。

限流知识《》

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,最终导致自身服务的瘫痪。

举个例子,在一个电商网站中,我们可能会将系统拆分成,用户、订单、库存、积分、评论等一系列的服务单元。用户创建一个订单的时候,在调用订单服务创建订单的时候,会向库存服务来请求出货(判断是否有足够库存来出货)。此时若库存服务因网络原因无法被访问到,导致创建订单服务的线程进入等待库存申请服务的响应,在漫长的等待之后用户会因为请求库存失败而得到创建订单失败的结果。如果在高并发情况之下,因这些等待线程在等待库存服务的响应而未能释放,使得后续到来的创建订单请求被阻塞,最终导致订单服务也不可用。

在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就会因依赖关系形成故障蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构就更加的不稳定。为了解决这样的问题,因此产生了断路器模式。

什么是断路器

断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。

熔断器设计中有三种状态,closed(关闭状态,流量可以正常进入)、open(即熔断状态,一旦错误达到阈值,熔断器将打开,拒绝所有流量)和half-open(半开状态,open状态持续一段时间后将自动进入该状态,重新接收流量,一旦请求失败,重新进入open状态,但如果成功数量达到阈值,将进入closed状态),见下图:

CLOSED关闭状态允许流量通过

OPEN打开状态:不允许流量通过,即处于降级状态,走降级逻辑。

HALF_OPEN半开状态:允许某些流量通过,并关注这些流量的结果,如果出现超时、异常等情况,将进入OPEN状态,如果成功,那么将进入CLOSED状态

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

Netflix Hystrix

在Spring Cloud中使用了Hystrix 来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

下面我们来看看如何使用Hystrix。

Netfix Hystrix可以通过两种方式引入:

  1. 从服务组件角度分:ribbon和Feign;
  2. 从代码方式角度分:hystrix通过设置fallback和fallbackFactory属性触发请求容灾降级;

准备工作

在开始加入断路器之前,我们先拿之前构建两个微服务为基础进行下面的操作,主要使用下面几个工程:

  • 《》
    • eureka-server工程:服务注册中心,端口1111
    • compute-service工程:服务单元,端口2223

在Ribbon为引入Hystirx之前

  • 依次启动eureka-server、compute-service、ribbon-consumer工程
  • 访问可以看到注册中心的状态
  • 访问,调用ribbon-consumer的服务,该服务会去调用compute-service的服务,计算出10+20的值,页面显示30
  • 关闭compute-service服务,访问,我们获得了下面的报错信息

具体的操作及关键代码如下:

 创建一个ribbon-consumer的项目,pom为:

4.0.0
com.dxz
ribbon-consumer
0.0.1-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.3.5.RELEASE
UTF-8
1.8
org.springframework.cloud
spring-cloud-starter-eureka-server
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-parent
Brixton.SR3
pom
import
org.springframework.boot
spring-boot-maven-plugin

启动类:

package com.dxz.ribbon;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@SpringBootApplication@EnableDiscoveryClientpublic class RibbonApplication {    @Bean    @LoadBalanced    RestTemplate restTemplate() {        return new RestTemplate();    }    public static void main(String[] args) {        SpringApplication.run(RibbonApplication.class, args);    }}

SpringMvc类:

package com.dxz.ribbon;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;@RestControllerpublic class ConsumerController {    @Autowired    RestTemplate restTemplate;        @RequestMapping(value = "/add", method = RequestMethod.GET)    public String add() {        return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();    }}

application配置:

spring.application.name=ribbon-consumerserver.port=2250eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

 

启动eureka-server,但不启动computer-service服务,启动ribbon-consumer服务并访问:

测试结果:

方式一:Ribbon中引入Hystrix

1、pom.xml中引入依赖hystrix依赖

org.springframework.cloud
spring-cloud-starter-hystrix

2、在eureka-ribbon的主类RibbonApplication中使用@EnableCircuitBreaker注解开启断路器功能

package com.dxz.ribbon;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@EnableCircuitBreaker@SpringBootApplication@EnableDiscoveryClientpublic class RibbonApplication {    @Bean    @LoadBalanced    RestTemplate restTemplate() {        return new RestTemplate();    }    public static void main(String[] args) {        SpringApplication.run(RibbonApplication.class, args);    }}

3、改造原来的服务消费方式,新增ComputeService类,在使用ribbon消费服务的函数上增加@HystrixCommand注解来指定回调方法。

package com.dxz.ribbon;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;@Servicepublic class ComputeService {    @Autowired    RestTemplate restTemplate;    @HystrixCommand(fallbackMethod = "addServiceFallback")    public String addService() {        return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20&sn=1", String.class).getBody();    }    public String addServiceFallback() {        return "error";    }}

4、提供rest接口的Controller从使用RestTemplate调用改为调用ComputeService的addService方法:

package com.dxz.ribbon;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;@RestControllerpublic class ConsumerController {    //@Autowired    //RestTemplate restTemplate;    @Autowired    private ComputeService computeService;        @RequestMapping(value = "/add", method = RequestMethod.GET)    public String add() {        //return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();        return computeService.addService();    }}
  • 验证断路器的回调
    • 依次启动eureka-server、compute-service、eureka-ribbon工程
    • 访问可以看到注册中心的状态
    • 访问,页面显示:30

  • 关闭compute-service服务后再访问,页面显示:error

由于Hystrix默认超时时间为2000毫秒,所以这里采用了0至3000的随机数以让有一定概率发生超时来触发断路器。分别将服务提供方和消费方修改如下:

服务提供方,springcloud-computer:

@RequestMapping(value = "/add", method = RequestMethod.GET)    public Integer add(@RequestParam Integer a, @RequestParam Integer b, @RequestParam Integer sn) {        ServiceInstance instance = client.getLocalServiceInstance();        Integer r = a + b;        try {            int sleepTime = new Random().nextInt(3000);            System.out.println("sleepTime=" + sleepTime);            Thread.sleep(sleepTime);        } catch (InterruptedException e) {            e.printStackTrace();        }        logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r + ",sn="+sn +",time="+ LocalDateTime.now());        return r;    }

 

服务消费方,ribbon-consumer:

package com.dxz.ribbon;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;@RestControllerpublic class ConsumerController {    //@Autowired    //RestTemplate restTemplate;    @Autowired    private ComputeService computeService;        @RequestMapping(value = "/add", method = RequestMethod.GET)    public String add() {        //return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();        Long start = System.currentTimeMillis();        String temp = computeService.addService();        Long end = System.currentTimeMillis();        System.out.println("computeService.addService()="+temp + ",user time=" +(end-start) + "毫秒");        return temp;    }}

结果:

随机出现熔断场景

方式二:Feign使用Hystrix

注意这里说的是“使用”,没有错,我们不需要在Feigh工程中引入Hystix,Feign中已经依赖了Hystrix,我们可以在未做任何改造前,尝试下面你的操作:

  • 依次启动eureka-server、compute-service、eureka-feign工程
  • 访问可以看到注册中心的状态
  • 访问,调用eureka-feign的服务,该服务会去调用compute-service的服务,计算出10+20的值,页面显示30
  • 关闭compute-service服务,访问,我们获得了下面的报错信息
Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Sat Jun 25 22:10:05 CST 2016There was an unexpected error (type=Internal Server Error, status=500).add timed-out and no fallback available.

使用@FeignClient注解中的fallback属性指定回调类如果您够仔细,会发现与在ribbon中的报错是不同的,看到add timed-out and no fallback available这句,或许您已经猜到什么,看看我们的控制台,可以看到报错信息来自hystrix-core-1.5.2.jar,所以在这个工程中,我们要学习的就是如何使用Feign中集成的Hystrix。

package com.dxz;import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;//@FeignClient("compute-service")@FeignClient(value = "compute-service", fallback = ComputeClientHystrix.class)public interface ComputeClient {    @RequestMapping(method = RequestMethod.GET, value = "/add")    Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);}
  • 创建回调类ComputeClientHystrix,实现被@FeignClient注解的接口,此时实现的方法就是对应@FeignClient接口中映射的fallback函数。
package com.dxz;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.RequestParam;@Componentpublic class ComputeClientHystrix implements ComputeClient {    @Override    public Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b) {        return -9999;    }}
  • 再用之前的方法验证一下,是否在compute-service服务不可用的情况下,页面返回了-9999。

三、fallbackFactory

fallbackFactory与fallback不同的是可以打印详细的错误信息

将原来的fallback替换成fallbackFactory

@FeignClient(value = "message-api",fallbackFactory = MessageApiFailFactory.class)public interface MessageApiFeignClient {    @RequestMapping(value = "/example/hello",method = RequestMethod.GET)    public String getMessage(@RequestParam("name") String name);}public interface MessageApiFeignFallBackFactoryClient extends MessageApiFeignClient{}@Componentpublic class MessageApiFailFactory implements FallbackFactory
{ public static final Logger logger = LoggerFactory.getLogger(MessageApiFailFactory.class); @Override public MessageApiFeignClient create(Throwable throwable) { logger.info("fallback; reason was: {}",throwable.getMessage()); return new MessageApiFeignFallBackFactoryClient(){ @Override public String getMessage(String name) { return "错误原因:"+throwable.getMessage(); } }; }}

同样进行本地调用

messageApiFeignClient.getMessage("zhangsan");

运行后服务调用方打印: 

错误原因:status 500 reading MessageApiFeignClient#getMessage(String); content: 
 {"timestamp":1508681203932,"status":500,"error":"Internal Server Error","exception":"java.lang.ArithmeticException","message":"/ by zero","path":"/example/hello"} 
另外配置文件下可以设置hystrix服务超时机制

#开启hystrix请求超时机制   也可以设置成永久不超时 hystrix.command.default.execution.timeout.enabled=falsehystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000

 

转载地址:http://puzum.baihongyu.com/

你可能感兴趣的文章
java_类型转换(转)
查看>>
EMC 存储 故障转移模式(Failover Mode)简介
查看>>
解决iis服务器 Server Application Error
查看>>
wget
查看>>
openjdk安装二
查看>>
信息、信息化与信息化营销
查看>>
https的网站用了百度分享后网站在浏览器中不安全解决方法
查看>>
我的友情链接
查看>>
Oracle shutdown immediate无法关闭数据库解决方法
查看>>
MySQL5.7杀手级新特性:GTID原理与实战
查看>>
iOS 分类思想(1)
查看>>
键盘中每个键的作用你知多少?
查看>>
HTML模板
查看>>
编程会不会没落、C语言会不会没落?
查看>>
ORACLE PGA(程序全局区)
查看>>
python -- 飞机大战(实验)
查看>>
云在天之南——我的七天七夜(感动普达措)
查看>>
LVM
查看>>
转 如何选择最适合你的NoSQL数据库
查看>>
NIO入门系列之第8章:连网和异步 I/O
查看>>