爲了防止雪崩效應,必須有一個強大的容錯機制。該容錯機制需實現如下兩點: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開銷會比較高。
信號量隔離通常僅適用於非網絡調用的隔離。