什么是服务雪崩?
单个服务发生故障,占用过多的系统资源,从而导致级联故障的情况称为服务雪崩。
什么是Hystrix?
在分布式环境中,许多服务依赖项中的一些必然会失败。(服务挂了)
Hystrix是一个库,通过添加延迟容忍和容错逻辑,控制这些分布式服务之间的交互。
Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
容错:允许犯错,在微服务开放中主要体现在服务故障。
简言之,Hystrix是一个实现容错机制的组件。【也是实现高可用的目的】
Hystrix的主要作用
- 为网络请求设置超时
- 使用断路器模式
什么是断路器模式?
家用空开就是一种断路器模式,前身是保险丝。假设某个电器负载过大而损坏,空开会跳闸,而保险丝会熔断。
假设没有空开或者保险丝呢?引起更大的电路故障,甚至导致火灾,再扩张可能会烧到邻居家的房子。
对于微服务来说同样如此,当某一个服务出现问题时,使用断路器关停服务,不会导致由于持续访问导致的资源占有从而引起其他服务的正常运行。
服务熔断与服务降级
服务熔断指的是当网络请求达到某一个阈值(可设置)时,为了防止服务过载,占用系统资源,暂停该服务的调用,使服务降级。【服务没挂,但是担心挂了,就让服务暂时休息一下】
服务降级涉及的范围更大,
- 超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况
- 失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
- 故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
- 限流降级
当我们去秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时开发者会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)。
如何理解服务熔断和服务降级的差异?
服务熔断的场景是请求次数过多而设计的一种保护策略。而服务降级是着眼于整个系统的各种问题(超时,故障等等)。服务熔断会引起服务降级。换句话说,熔断是降级的一部分。
@HystrixCommand注解
默认开启线程池隔离,服务熔断,服务降级
接着上次的工程做些修改:【和feign结合使用】
https://github.com/HCJ-shadow/Feign
- 新建工程msc-consumer-hystrix-80
- pom依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <!-- Eureka客户端启动需要依赖web模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <!-- <dependency>--> <!-- <groupId>org.springframework.cloud</groupId>--> <!-- <artifactId>spring-cloud-starter-openfeign</artifactId>--> <!-- </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>
controller
package zkrun.top.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import zkrun.top.service.Hystrix_FeignService; @RestController public class Hystrix_FeignController { @Autowired Hystrix_FeignService hystrix_feignService; @RequestMapping(value = "/feign/info/get",method = RequestMethod.GET) @HystrixCommand(fallbackMethod = "hystrix_fallback") public String request() { return this.hystrix_feignService.request(); } public String hystrix_fallback() { return "当前服务故障,服务熔断已启动!"; } }
application.yaml
server: port: 80 eureka: client: service-url: defaultZone: http://eureka6001.com:6001/eureka/,http://eureka6002.com:6002/eureka/,http://eureka6003.com:6003/eureka/ spring: application: name: hystrix-consumer
- 主启动类【@EnableCircuitBreaker】
package zkrun.top; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients @EnableEurekaClient @EnableCircuitBreaker public class App_Consumer_Hystrix_Feign_80 { public static void main(String[] args) { SpringApplication.run(App_Consumer_Hystrix_Feign_80.class, args); } }
运行测试:
- 启动三个Eureka,
- 启动5001,5002,5003
- 启动hystrix80
测试服务熔断
使用Jmeter
http://jmeter.apache.org/download_jmeter.cgi
解压后双击bin下面的jmeter.bat即可
设置线程数为100
设置相关信息
执行测试,查看结果树
额嗯,完全抗的住啊!!!
设置线程数为500:
出现熔断情况。
测试服务降级
设置故障降级,把5001关停
调用降级方法,重复几次之后,将不再访问5001。【Ribbon的RetryRule策略】
假设重启服务,
即可正常访问。
小结:
- 为了保持高可用,应用可以配置多份,这样即使故障一台,对外仍旧可以保持可用性。但是随之而来的是数据库的一致性问题。CAP理论的A与C只能选择一个也是这个原理。根据实际的业务情况,哪些业务是必须保持高可用的,而哪些是必须保持一致性的,需要进一步分析和判断。
- Hystrix实现服务的熔断和降级策略的自由度很高,理解其原理,搭配Feign中集成的RIbbon访问算法,可以实现更高的扩展和组合。
代码参考:https://github.com/HCJ-shadow/Hystrix