为了防止雪崩效应,必须有一个强大的容错机制。该容错机制需实现如下两点:java
必须为网络请求设置超时。web
正常状况下,一个远程调用通常在及时毫秒内就能获得响应了。若是依赖的服务不可用或者网络有问题,那么响应时间就会变得特别长。spring
一般状况下,一次远程调用对应着一个线程/进程。若是响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资源,若是得不到释放的线程/进程约积越多,资源就会逐渐被耗尽,最终致使服务的不可用。apache
若是对某个微服务的请求有大量超时(经常说明该微服务不可用),再去让新的请求访问该服务已经没有任何意义,只会无所谓消耗资源。设计模式
例如,设置了超时时间为1秒,若是短期内有大量的请求没法在1秒内获得响应,就没有必要再去请求依赖的服务了。服务器
断路器可理解为对容易致使错误的操做的代理。网络
这种代理可以统计一段时间内调用失败的次数,并决定是正常请求依赖的服务仍是直接返回。架构
断路器能够实现快速失败,若是它在一段时间内检测到许多相似的错误(例如超时),就会在以后的一段时间内,强迫对该服务的调用快速失败,即再也不请求所依赖的服务。并发
这样,应用程序就无需再浪费cpu时间去等待长时间的超时。app
断路器也可自动诊断是否已经恢复正常。若是发现依赖的服务已经恢复正常,那么就会恢复请求该服务。
使用这种方式,就能够实现微服务的“自我修复”——当依赖的服务不正常打开断路器时快速失败,从而防止雪崩效应;
当发现依赖的服务恢复正常时,又会恢复请求。
断路器状态转换逻辑:
- 正常状况下,断路器关闭,可正常请求依赖的服务
- 当一段时间内,请求失败率达到必定阀值(例如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。
- 断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可容许一个请求访问依赖的服务。若是该请求可以调用成功,则关闭断路器;不然继续保持打开状态。
熔断器的原理很简单,如同电力过载保护器。
它能够实现快速失败,若是它在一段时间内侦测到许多相似的错误,会强迫其之后的多个调用快速失败,
再也不访问远程服务器,从而防止应用程序不断地尝试执行可能会失败的操做,使得应用程序继续执行而不用等待修正错误,
或者浪费CPU时间去等到长时间的超时产生。熔断器也可使应用程序可以诊断错误是否已经修正,若是已经修正,应用程序会再次尝试调用操做。
熔断器模式就像是那些容易致使错误的操做的一种代理。这种代理可以记录最近调用发生错误的次数,而后决定使用容许操做继续,或者当即返回错误。
Hystrix是一个实现了超时机制和断路器模式的工具类库。
是由Netfix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提高系统可用性与容错性。
Hystrix主要经过如下几点实现延迟和容错。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>microservice-consumer-movie-ribbon-with-hystrix</artifactId> <packaging>jar</packaging> <parent> <groupId>com.itmuch.cloud</groupId> <artifactId>microservice-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> </dependencies> </project>
配置文件:
spring: application: name: microservice-consumer-movie-ribbon-with-hystrix server: port: 8010 eureka: client: healthcheck: enabled: true serviceUrl: defaultZone: http://user:password123@localhost:8761/eureka instance: prefer-ip-address: true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
启动类
package com.itmuch.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class ConsumerMovieRibbonApplication { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerMovieRibbonApplication.class, args); } }
业务类:
package com.itmuch.cloud.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import com.itmuch.cloud.entity.User; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; @RestController public class MovieController { @Autowired private RestTemplate restTemplate; @GetMapping("/movie/{id}") @HystrixCommand(fallbackMethod = "findByIdFallback") public User findById(@PathVariable Long id) { return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class); } public User findByIdFallback(Long id) { User user = new User(); user.setId(0L); return user; } }
实体类:
package com.itmuch.cloud.entity; import java.math.BigDecimal; public class User { private Long id; private String username; private String name; private Short age; private BigDecimal balance; public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Short getAge() { return this.age; } public void setAge(Short age) { this.age = age; } public BigDecimal getBalance() { return this.balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } }
测试:
1 启动eureka
2 启动user微服务
3 启动movie微服务
4 访问http://localhost:8010/user/1,
结果以下
{"id":1,"username":"user1","name":"张三","age":20,"balance":100.00}
5 中止user微服务
6 再次访问http://localhost:8010/user/1,
结果以下
{"id":0,"username":null,"name":null,"age":null,"balance":null}
说明当前微服务不可用,进入回退方法。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>microservice-consumer-movie-ribbon-with-hystrix-propagation</artifactId> <packaging>jar</packaging> <parent> <groupId>com.itmuch.cloud</groupId> <artifactId>microservice-spring-cloud</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> </dependencies> </project>
配置文件:
spring: application: name: microservice-consumer-movie-ribbon-with-hystrix-propagation server: port: 8010 eureka: client: healthcheck: enabled: true serviceUrl: defaultZone: http://user:password123@localhost:8761/eureka instance: prefer-ip-address: true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
启动类:
package com.itmuch.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class ConsumerMovieRibbonApplication { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerMovieRibbonApplication.class, args); } }
业务类:
package com.itmuch.cloud.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import com.itmuch.cloud.entity.User; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; @RestController public class MovieController { @Autowired private RestTemplate restTemplate;
//表示@HystrixCommand与findById方法会在同一个线程中调用
//若是不配合的话findById是一个线程,@HystrixCommand是一个隔离的线程至关于两个线程
//正常状况下不须要配置,等抛异常了在配置 @GetMapping("/movie/{id}") @HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties = @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")) public User findById(@PathVariable Long id) { return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class); } public User findByIdFallback(Long id) { User user = new User(); user.setId(0L); return user; } }
实体类:
package com.itmuch.cloud.entity; import java.math.BigDecimal; public class User { private Long id; private String username; private String name; private Short age; private BigDecimal balance; public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Short getAge() { return this.age; } public void setAge(Short age) { this.age = age; } public BigDecimal getBalance() { return this.balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } }
需导入以下jar:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
访问: http://localhost:8010/movie/1
结果以下:{"id":1,"username":"user1","name":"张三","age":20,"balance":100.00}
访问: http://localhost:8010/health
获得以下结果:
Hystrix的状态是UP,也就是一切正常,此时断路器是关闭的。
咱们发现,尽管执行了回退逻辑,返回了默认用户,但此时Hystrix的状态依然是UP,这是由于咱们的失败率还没达到阈值(默认是5秒内20次失败),
这里再次强调,执行回退逻辑并不表明断路器已经打开。请求失败、超时、被拒绝以及断路器打开时都会执行回退逻辑。
Hystrix的隔离策略有两种:分别是线程隔离和信号量隔离。
Hystrix中默认而且推荐使用线程隔离(THREAD),由于这种方式有一个除网络超时之外的额外保护层。
通常来讲,只有当调用负载很是高时(例如每一个实例每秒调用数百次)才须要使用信号量隔离,由于这种场景下使用THREAD开销会比较高。
信号量隔离通常仅适用于非网络调用的隔离。