分布式系統中一定會遇到的一個問題:服務雪崩效應或者叫級聯效應
什么是服務雪崩效應呢?
在一個高度服務化的系統中,我們實現的一個業務邏輯通常會依賴多個服務,比如:
商品詳情展示服務會依賴商品服務, 價格服務, 商品評論服務. 調用三個依賴服務會共享商品詳情服務的線程池. 如果其中的商品評論服務不可用, 就會出現線程池里所有線程都因等待響應而被阻塞, 從而造成服務雪崩. 如圖所示:
簡單理解: 就是商品評論服務耗時假如是15S,那么在高並發的時候,其他服務很快就做出響應並把線程回收但是商品評論服務需要10S。在這10S內100個線程都會集中被消耗在商品評論服務,就造成商品評論服務沒有線程對外提供服務了。
服務雪崩效應:因服務提供者的不可用導致服務調用者的不可用,並將不可用逐漸放大的過
程,就叫服務雪崩效應
導致服務不可用的原因有幾點: 程序Bug,大流量請求,硬件故障,緩存擊穿
【大流量請求】:在秒殺和大促開始前,如果准備不充分,瞬間大量請求會造成服務提供者的不可用.
【硬件故障】:可能為硬件損壞造成的服務器主機宕機, 網絡硬件故障造成的服務提供者的不可訪問.
【緩存擊穿】:一般發生在緩存應用重啟, 緩存失效時高並發, 所有緩存被清空時,以及短時間內大量緩存失效時. 大量的緩存不命中, 使請求直擊后端,造成服務提供者超負荷運行,引起服務不可用。
用戶重試/代碼邏輯重試,用戶重試:在服務提供者不可用后, 用戶由於忍受不了界面上長時間的等待,會不斷刷新頁面甚至提交表單,或者是代碼有重試策略等等
那么,歸根結底導致雪崩效應的最根本原因是:大量請求線程同步等待造成的資源耗盡
解決方案
1. 超時機制
2. 服務限流
3. 服務熔斷
4. 服務降級
先啟動Eureka 注冊中心,代碼就不上了,截圖:
order微服務工程
pom.xml :
<?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> <groupId>com.tuling.cloud</groupId> <artifactId>microservice-consumer-order-ribbon-hystrix-fallback</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>06-ms-consumer-order-ribbon-hystrix-fallback</name> <!-- 引入spring boot的依賴 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <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.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- <artifactId>spring-cloud-starter-eureka</artifactId> 依賴了 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> --> <!-- 此包包含了eurekaclient,ribbon--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</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> </dependencies> <!-- 引入spring cloud的依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Edgware.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- 添加spring-boot的maven插件 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
OrderController.java:
package com.jiagoushi.cloud.study.user.controller; import com.jiagoushi.cloud.study.user.entity.User; import com.jiagoushi.cloud.study.user.feign.UserFeignClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; /** * 演示 user服務掛了超時和執行請求超時 */ @RestController public class OrderController { private static final Logger logger = LoggerFactory.getLogger(OrderController.class); @Autowired private UserFeignClient userFeignClient; @Autowired private RestTemplate restTemplate; /** * Hystrix調用接口默認1秒超時,超時后會自動執行降級方法,可在文件配置,配置文件配置是全局的, * @HystrixCommand(fallbackMethod = "findByIdFallback") 注解也可以配置超時 */ @HystrixCommand(fallbackMethod = "findByIdFallback") // 基於注解的hystrix 推薦使用 @GetMapping("/user/{id}") public User findById(@PathVariable Long id) { logger.info("================請求用戶中心接口=============="); return this.restTemplate.getForObject("http://microservice-provider-user/" + id, User.class); } // 降級的方法 public User findByIdFallback(Long id) { User user = new User(); user.setId(-1L); user.setName("降級用戶"); return user; } }
ConsumerOrderApplication.java:
package com.jiagoushi.cloud.study; 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.cloud.netflix.feign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication @EnableCircuitBreaker//開啟斷路器功能 public class ConsumerOrderApplication { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerOrderApplication.class, args); } }
user微服務:
package com.tuling.cloud.study.controller; import java.util.Random; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.tuling.cloud.study.entity.User; import com.tuling.cloud.study.repository.UserRepository; @RestController public class UserController { private final Logger logger = Logger.getLogger(getClass()); @Autowired private UserRepository userRepository; @Autowired private Registration registration; @GetMapping("/{id}") public User findById(@PathVariable Long id) throws Exception { logger.info("用戶中心接口:查詢用戶"+ id +"信息"); //模擬系統執行速度很慢的情況 Thread.sleep(5000); User findOne = userRepository.findOne(id); return findOne; } @GetMapping("/getIpAndPort") public String findById() { return registration.getHost() + ":" + registration.getPort(); } }
user微服務在停止或者阻塞5s 的時候,order服務會走降級方法
如何想修改接口超時間時間可以在order調用方修改yml 文件:
server: port: 9010 spring: application: name: microservice-consumer-order eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 #命令執行超時時間,默認1000ms,就是調接口的響應時間超過3S就執行降級,不管提供者是否掛機還是延遲超過時間就走降級