微服务实战 之 服务容错保护 Spring Cloud Hystrix

Spring Cloud Hystrix

整体描述:

在微服务架构中,我们将服务拆分成了许多独立的单元,这些服务部署在不同的地方,服务之间通过服务注册,服务消费互相调用。但是在服务的调用过程当中可能由于网络的原因或者是被调用方自身的故障出现响应延迟的情况。而这种情况也会导致调用方出现响应延迟,而此时如果请求不断,会造成请求的积压 从而导致服务瘫痪。

由于存在着许多的服务单元,单一的服务出现问题,可能会因为依赖的关系导致故障的蔓延。举个例子来说:在一个项目中存在着 订单服务,库存服务,商品服务... 在创建一个订单的时候,需要判断库存是否足够,扣减库存,订单服务需要依赖于库存服务,当在调用的时候,库存服务由于自身的处理逻辑处理慢,导致订单服务长时间获取不到响应,创建订单失败。此时在高并发的状况下,调用库存服务的线程因为等待而被挂起,后续请求创建订单的线程被阻塞,最终导致订单服务也不可用。这样的情况比单体应用更加不稳定,为了解决这样的情况出现了断路器这样的服务容错机制。

针对上述的问题,Spring Cloud Hystrix实现了断路器,通过对调用的请求进行监控,向调用方返回一个错误的信息而不是长时间的等待,这样就不会由于线程长时间的等待不释放造成故障在分布式环境中的蔓延。

Spring Cloud Hystrix 实现了断路器,线程隔离等一系列的服务保护功能。他是基于Netflix的开源框架 Hystrix实现的,该框架的目标在于通过控制那些访问远程系统,服务,第三方框架的节点,对延迟和故障有更加强大的容错能力。Hystrix具备服务降级,服务熔断,线程和信号隔离,请求缓存,请求合并以及服务监控等强大的功能。

实例展示:

首先构建一个服务互相调用的场景:

需要一个服务注册中心  三个Eureka Client,其中包括一个服务调用方我们使用Ribbon来实现,还有两个提供相同服务的实例,启动在不同的端口。

注册中心:

首先是注册中心: 启动在8080端口。

server:
  port: 8080
eureka:
  instance:
    hostname: localhost
  server:
  #关闭自我保护
    enable-self-preservation: false
  client:
    #关闭向注册中心注册自己
    register-with-eureka: false
    #由于自己就是注册中心  自己就维护着服务实例 不需要去检索服务
    fetch-registry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

通过在浏览器中访问 http://localhost:8080 ,可以发现当前还没有服务注册。(关于服务提供者服务消费者的代码可以看 Spring Cloud Ribbon 服务消费者

服务消费者:

启动在8092端口,服务名为:consumer。

server:
  port: 8092
spring:
  application:
    name: consumer

eureka:
  instance:
    hostname: localhost
  client.serviceUrl.defaultZone: http://localhost:8080/eureka/

启动项目之后刷新 http://localhost:8080  发现消费者已经成功的注册了。

服务提供方

我们在这里启动两个提供方的实例来进行测试:(通过指定端口为随机数启动两次,同时指定不同的服务的实例id)

server:
  port: ${random.int[1000,2000]}
spring:
  application:
    name: hello-service
eureka:
  instance:
    hostname: localhost
    instance-id: ${random.int[100,200]}
  client.serviceUrl.defaultZone: http://localhost:8080/eureka/

再次观察注册中心 

发现和我们预想中的一样 ,提供方有两个实例。

实际测试中出现的问题:在指定多实例的随机端口时发现通过 ${random.int}的方式来指定 在实际的使用中注册中心的端口与实际启动的端口不符?  改用指定随机端口 server.port=0   这样设置的话 是正常的。   !!!!

大家可以自己进行测试一下。  http://localhost:8080/eureka/apps  可以通过这样的方式来查看实际的实例详情。

 

断路器的测试详情:

首先进行消费者端的测试:直接在浏览器中访问:http://localhost:8092/consumer-hello

在没有加入断路器的情况下,关闭一个服务提供者的实例,浏览器会出现下面的输出:

添加断路器:

①首先在服务消费方的pom.xml 中添加hystrix的依赖:

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

②在主类上添加注解: @EnableCircuitBreaker

或者干脆将主类上的注解直接换为 @SpringCloudApplication  点进去观察源码  可以发现包含有注解 @SpringBootApplication  @EnableDiscoveryClient  @EnableCircuitBreaker  说明一个典型的SpringCloud服务 是包含服务注册 服务发现以及断路器的。

③修改消费者端原先的代码 增加原来的逻辑处理到service层;

package com.wc.study.ribbon.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.wc.study.ribbon.bean.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * Created by wangchen on 2019/1/23.
 */
@Service
public class HelloService {

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "errorHello")
    public String hello(){
        return restTemplate.getForObject("http://HELLO-SERVICE/hello?name={1}",String.class,"test");
    }

    @HystrixCommand(fallbackMethod = "saveFallBack")
    public String save(){
        Student student = new Student("乔巴",12);
        ResponseEntity<Student> entity = restTemplate.postForEntity("http://HELLO-SERVICE/save",student,Student.class);
        return entity.getBody().toString();
    }

    private String errorHello(){
        return "error";
    }

    private String saveFallBack(){
        Student student = new Student("error",12);
        return student.toString();
    }
}
package com.wc.study.ribbon.controller;

import com.wc.study.ribbon.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by wangchen on 2019/1/22.
 */
@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/consumer-hello")
    public String index(){
        return helloService.hello();
    }

    @GetMapping("/consumer-save")
    public String save(){
        return helloService.save();
    }
}

重新启动之后,测试访问可以正常进行。

 

测试开始:

首先我们将提供方的服务实例断开一个,直接停止,然后再次调用消费者端的代码会发现直接返回了error信息 而没有报错,说明我们的断路器起作用了。

然后我们重试一种方法,模仿服务提供方处理逻辑缓慢,直接放线程随机的阻塞 1~3 s。并在消费者端打印执行的时间。

生产者端代码改动:

消费者端代码改动:

观察打印的时间:

会发现时间在超过2s的时候 会触发断路器打印出error。

 

在什么情况下可以使用Hystrix

在实际的使用中我们可能会用Hystrix命令来实现服务的降级,但是在一些情况下也可以不使用:

  • 执行写操作的命令。当使用写操作的时候我们仅仅通知使用者就可以了
  • 执行批处理任务或者离线计算的任务。当需要生成一份报告或者需要离线计算的时候,我们可以通知调用者重试。

 

关于Hystrix的异常处理:

忽略某些异常:

在碰到调用过程中出现异常时,Hystrix会调用我们在@HystrixCommand(fallbackMethod="XXX") 配置的XXX方法。我们也可以配置在遇到什么异常时不触发。 ignoreException={}

进行异常捕获:

在调用的过程中如果触发了异常,我们需要知道具体的异常情况的话,也可以直接在fallbackMethod 指定的方法中 直接传入参数Throwable 即可进行异常捕获。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值