服务之间的调用,如果出现网络抖动、服务提供者宕机、连接超时等情况,就会造成服务消费者的请求出现错误,同时造成外界对服务消费者访问的报错,会出现一连串的反应。这种情况就需要一种容错处理机制,从而提升服务的可用性。比如,双十一的时候,天猫、淘宝等网站的并发量大的惊人,对于各个服务来说负载都非常大,尤其对于基础服务来说,因此,这些商城在促销时会选择性地给一些服务容错的空间,从而保证另一些服务的高可用性,比如涉及支付的服务肯定需要保证高可用性,而订单物流信息服务则可以给予容错空间,比如可以将该订单号放入消息队列,延迟处理该订单的物流信息。这是服务的降级以及容错处理。服务的容错处理是为了整体服务的高可用,避免出现级联故障从而导致雪崩效应。在SpringCloud生态中,则使用Hystrix来实现容错处理。
1.Hystrix
Hystrix主要用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
1.包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。
2.跳闸机制:当服务的错误率超过一定阈值时,Hystrix会停止该服务一段时间。
3.资源隔离:Hystrix为每个依赖维护了一个小型的线程池。
4.监控:Hystrix可以监控运行指标和配置的变化。
5.回退机制:当请求失败、超时、被拒绝或者断路器打开时,执行相应的回退逻辑。
6.自我修复:断路器打开之后并不是直接打开,而是进入半开状态。等到服务可用,才会真正打开。
2.整合使用Hystrix的案例
这里主要介绍普通服务调用整合Hystrix和Feign整合Hystrix两种情况。首先列出两者公共使用的pojo类,yaml配置文件,pom文件以及启动类。
1.pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.carson</groupId>
<artifactId>microservice-teacher-hystrix</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>microservice-teacher-hystrix</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<!--引入Hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--引入Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.application.yaml
server:
port: 8091
spring:
application:
name: microservice-teacher-hystrix
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #Eureka Server
instance:
prefer-ip-address: true #将自己的ip注册到Eureka Server上
microservice-student:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
feign:
hystrix:
enabled: true
3.pojo类:
public class Student {
private Long studentid;
private String studentname;
private String grade;
private Integer age;
private String sex;
public Long getStudentid() {
return studentid;
}
public void setStudentid(Long studentid) {
this.studentid = studentid;
}
public String getStudentname() {
return studentname;
}
public void setStudentname(String studentname) {
this.studentname = studentname;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
4.启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableHystrix
@EnableFeignClients
public class MicroserviceTeacherHystrixApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(MicroserviceTeacherHystrixApplication.class, args);
}
}
注意该启动类两个注解:@EnableHystrix是启用断路器 @EnableFeignClients则是启用Feign组件,服务启动,便会全局扫描。
另外,restTemplate方法是为了给普通模式通过RestTemplate调用URL获取服务使用的。
2.1普通模式整合Hystrix
通常情况下,调用服务就是使用URL拼接的形式,在方法中直接通过RestTemlate的方法获取服务。对于这种情况,Hystrix会通过@HystrixCommand注解在需要断路器机制的方法上注释,属性fallbackMethod则是方法出错的回退方法。具体Controller类如下:
@RestController
public class TeacherController {
private static final Logger logger = LoggerFactory.getLogger(TeacherController.class);
@LoadBalanced
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@HystrixCommand(fallbackMethod = "fallbackMethod")
@RequestMapping(value = "/student/{studentid}",method = RequestMethod.GET)
public Student findStudentInfo(@PathVariable Long studentid){
logUserInstance();
return this.restTemplate.getForObject("http://microservice-student/"+studentid,Student.class);
}
public Student fallbackMethod(Long studentid){
Student student = new Student();
student.setStudentid(-1001L);
student.setStudentname("Admin");
return student;
}
@RequestMapping(value = "/log-student-instance",method = RequestMethod.GET)
public void logUserInstance(){
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-student");
System.out.println("serviceId:"+serviceInstance.getServiceId()+" host:"+serviceInstance.getHost()+" port:"+serviceInstance.getPort());
TeacherController.logger.info("{}:{}:{}",serviceInstance.getServiceId(),serviceInstance.getHost(),serviceInstance.getPort());
}
}
运行启动类,启动服务,浏览器中访问:http://localhost:8091/student/100053 由于数据库中没有这个studentid,则请求会报错,从而运行回退方法,运行结果如下:
2.2Feign整合Hystrix
由于Feign组件包含Hystrix机制,因此不需要单独在启动类使用@EnableHystrix和在相应方法上使用@HystrixCommand注解。只需要使用Feign组件需要的注解,并且在配置文件中打开Hystrix: feign.hystrix.enabled=true.
Controller类:
@RestController
public class TeacherFeignController {
@Autowired
private StudentFeignClient studentFeignClient;
@RequestMapping(value = "/feignstudent/{studentid}",method = RequestMethod.GET)
public Student findById(@PathVariable Long studentid){
return studentFeignClient.findById(studentid);
}
}
feignclient:
@FeignClient(name = "microservice-student",fallback =StudentFeignFallback.class )
@Repository
public interface StudentFeignClient {
@RequestMapping(value="/{studentid}",method = RequestMethod.GET)
public Student findById(@PathVariable("studentid") Long studentid);
}
降级类:
@Component
public class StudentFeignFallback implements StudentFeignClient {
@Override
public Student findById(Long studentid) {
Student student = new Student();
student.setStudentid(-1111L);
student.setStudentname("admin");
return student;
}
}
注意:a.feignclient类中使用了@FeignClient注解,该注解的fallback的值是类,该类即为降级类。整合Feign必须用类因为FeignClient接口是接口,没有方法体,无法针对具体方法进行回退操作,只能使用类层面。
b.降级类必须实现FeignClient接口
c.降级类必须使用@Component注解
运行启动类,运行服务,在浏览器中输入:http://localhost:8091/feignstudent/100053 触发降级操作,得到如下所示:
通过以上例子对Hystrix使用例子,可以了解到使用了Hystrix组件进行容错处理,可以使服务在不可用时,进行回退操作或者降级处理,从而保证整体服务的高可用性。