【Spring Cloud学习之六】断路器-Hystrix
环境
eclipse 4.7
jdk 1.8
Spring Boot 1.5.2
Spring Cloud 1.2
一、服务雪崩
1、什么是服务雪崩
分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况, 这种现象被称为服务雪崩效应。
服务雪崩的原因:
(1)某几个机器故障:例如机器的硬驱动引起的错误,或者一些特定的机器上出现一些的bug(如,内存中断或者死锁);
(2)服务器负载发生变化:某些时候服务会因为用户行为造成请求无法及时处理从而导致雪崩,例如阿里的双十一活动,若没有提前增加机器预估流量则会造服务器压力会骤然增大而挂掉;
(3)人为因素:比如代码中的路径在某个时候出现bug;
2、服务雪崩应对策略
针对造成服务雪崩的不同原因, 可以使用不同的应对策略:
(1)流量控制(网关限流、用户交互限流、关闭重试)
(2)改进缓存模式
(3)服务自动扩容
(4)服务调用者降级服务
3、解决或缓解服务雪崩的方案
一般情况对于服务依赖的保护主要有3中解决方案:
(3.1)熔断模式:这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
在熔断的设计主要参考了hystrix的做法。其中最重要的是三个模块:熔断请求判断算法、熔断恢复机制、熔断报警
(1)熔断请求判断机制算法:使用无锁循环队列计数,每个熔断器默认维护10个bucket,每1秒一个bucket,每个blucket记录请求的成功、失败、超时、拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。
(2)熔断恢复:对于被熔断的请求,每隔5s允许部分请求通过,若请求都是健康的(RT<250ms)则对请求健康恢复。
(3)熔断报警:对于熔断的请求打日志,异常请求超过某些设定则报警
(3.2)隔离模式:这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火烧光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。
隔离的方式一般使用两种
(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
(2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
(3.3)限流模式:上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。
超时分两种,一种是请求的等待超时,一种是请求运行超时。
(1)等待超时:在任务入队列时设置任务入队列时间,并判断队头的任务入队列时间是否大于超时时间,超过则丢弃任务。
(2)运行超时:直接可使用线程池提供的get方法
二、断路器-Hystrix
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用(RPC)。为了保证其高可用,单个服务又必须集群部署。由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务积压,导致服务瘫痪,甚至导致服务“雪崩”。
为了解决这个问题,就出现断路器模型。Hystrix 是一个帮助解决分布式系统交互时超时处理和容错的类库, 它同样拥有保护系统的能力。
1、断路器机制
断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
2、Fallback
Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.
3、资源隔离
在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池. 这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响. 但是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.
补充概念:
什么是服务降级?
所谓的降级指的是当服务的提供方不可使用的时候,程序不会出现异常,而会出现本地的操作调用
三、模拟雪崩效应
1、改造service-member:
controller: 模拟3秒响应延迟
package com.wjy.controller; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MemberController { private static int count = 0; @Value("${server.port}") private String serverPort; @RequestMapping("/getUserList") public List<String> getUserList() { try { //模拟 假定getUserList需要3秒返回 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } count ++ ; System.out.println("count:"+count); List<String> listUser = new ArrayList<String>(); listUser.add("zhangsan"); listUser.add("lisi"); listUser.add("wjy"); listUser.add("COUNT:"+count); listUser.add("端口号:"+serverPort); return listUser; } @RequestMapping("/getMemberServiceApi") public String getMemberServiceApi() { return "this is 会员 服务工程"; } }
2、改造service-order-feign:添加getOrderInfo方法
package com.wjy.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.wjy.service.MemberFeign; @RestController public class OrderFeginController { @Autowired private MemberFeign memberFeign; @RequestMapping("/getFeignOrderMemberAll") public List<String> getToOrderMemberAll(){ System.out.println("order fegin 工程调用member工程"); return memberFeign.getToOrderMemberAll(); } @RequestMapping("/getOrderInfo") public String getOrderInfo() { return "getOrderInfo"; } }
修改application.yml,设置tomcat最大接线程数:
eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/ #### tomcat最大接线程数 server: port: 8765 tomcat: max-threads: 50 spring: application: name: service-order-feign
3、使用eureka-server做注册中心:
访问:http://localhost:8765/getOrderInfo 无延迟,迅速响应。
4、使用apache-jmeter进行压力测试,模拟100次/秒请求:http://localhost:8765/getFeignOrderMemberAll
效果:再次访问http://localhost:8765/getOrderInfo 出现延迟。
四、解决雪崩效应
1、rest调用方式(sercice-order)
(1)修改sercice-order的pom.xml,引入Hystrix依赖
<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.wjy</groupId> <artifactId>sercice-order</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </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.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!-- ribbon 负载均衡 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- hystrix 断路器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </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>Dalston.RC1</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> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>
(2)修改OrderMemberService.java
package com.wjy.service; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; @Service public class OrderMemberService { @Autowired private RestTemplate restTemplate; //@HystrixCommand 作用:服务发生错误,回调方法。 @HystrixCommand(fallbackMethod = "orderError") public List<String> getOrderUserAll() { return restTemplate.getForObject("http://service-member/getUserList", List.class); } public List<String> orderError() { List<String> listUser = new ArrayList<String>(); listUser.add("not orderUser list"); return listUser; } }
(3)修改启动类
package com.wjy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableEurekaClient @SpringBootApplication //@EnableHystrix 启动断路器 @EnableHystrix public class OrderApp { public static void main(String[] args) { SpringApplication.run(OrderApp.class, args); } /** * 在工程的启动类中,通过@EnableDiscoveryClient向服务中心注册; * 并且向程序的ioc注入一个bean: restTemplate; * 并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。 */ @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }
2、feign调用方式(service-order-feign)
(1)Fegin已经集成了Hystrix不需更改pom.xml
(2)修改MemberFeign
package com.wjy.service; import java.util.List; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; @FeignClient(value="service-member",fallback=MemberFeignService.class) public interface MemberFeign { @RequestMapping("/getUserList") public List<String> getToOrderMemberAll(); }
(3)新增MemberFeignService.java
package com.wjy.service; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Component; @Component public class MemberFeignService implements MemberFeign { @Override public List<String> getToOrderMemberAll() { List<String> listUser = new ArrayList<String>(); listUser.add("not orderUser list"); return listUser; } }
(4)修改application.yml
eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/ #### tomcat最大接线程数 server: port: 8765 tomcat: max-threads: 50 spring: application: name: service-order-feign #开启hystrix feign: hystrix: enabled: true ###设置超时时间 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 2000
测试验证:
将超时时间改为4000 (service-member MemberController.java==>getUserList 限定3秒的休眠时间)就正常访问了。