在微服务实战这本书中提到过一个健康的微服务架构,一定是可伸缩的,弹性的。可伸缩的的无非是服务从单机变成了集群,可以根据服务的业务能力把控住服务分片的数量。自然可伸缩的也不是今天要讨论的重点,下一个弹性的才是今天要Battle的课题。
什么是弹性的?
一个项目中所有的服务虽然是独立的,但是服务之间相互的调用是不可避免的,当被调用服务出现了网络故障或是访问DB的时间过长导致了一个服务同时堆积了大量的线程资源而得不到释放,被调用的服务自然会奔溃,发起服务调用的服务也会因为远程调用得不到返回造成级联故障,如果级联的服务数量比较多的话,服务雪崩自然也是不可避免的。
自然出现了问题就要有对应的解决方案,为了不让上述的级联故障产生就要让远程调用在一个合理的时间内快速返回,如果是因为超时而返回自然要提供一组后备的数据,如果一段时间内出现了好多调用失败的线程,那么对于这条服务调用的链路自然要进行调用的限制去为服务的恢復争取时间,为了不影响被调用服务的彻底崩溃,服务中的资源自然要做一些隔离。上述方案既是对弹性的这个概念提供的一些思路,而总结起来说的话,一个弹性的微服务架构要具有完善的后备模式,熔断模式和舱壁模式。
后备模式–>服务降级
服务降级说明
- 服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级,以此环节服务器的压力,以保证核心任务的进行。同时保证部分甚至大部分任务客户能得到正确的相应。也就是当前的请求处理不了了或者出错了,给一个默认的返回。
服务降级图示
Hystrix服务降级的实现
POM依赖
因为Hystrix的POM依赖都是一样的下面的示例就不再赘述。
代码语言:javascript复制<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置文件
因为微服务的远程调用基本上都是OpenFeign来负责,所以这里的配置文件主要是OpenFeign对于Hystrix的配置支持。
代码语言:javascript复制#开启
feign:
hystrix:
enabled: true
# feign超时控制
#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout:
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout:
# 全局配置超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds:
#feign日志增强
logging:
level:
com.jmy.springcloud.feign.PaymentFeignService: debug # feign日志以什么级别监控哪个接口
yaml
启动类
代码语言:javascript复制@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix // 消费服务启用Hystrix
@EnableCircuitBreaker
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
业务类
代码语言:javascript复制 @GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(/*fallbackMethod = "paymentTimeOutFallbackMethod",*/commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
其实Hystrix的核心就是在资源上加上@HystrixCommand这个注解,而这个注解里面的一个又一个属性就是针对于后备模式,熔断模式和舱壁模式的一个又一个实现。对于降级模式的实现就是fallbackMethod这个属性喽,@HystrixProperty(name=“execution.isolation.thread.timeoutInMilliseconds”,value=“3000”)在服务调用超过3秒就会使用fallbackMethod中使用的方法进行一个快速的返回。
不过上面的降级方法和咱们的业务代码耦合到了一起就会显得咱们的业务代码非常的臃肿,所以OpenFein看不下去了就提供了如下的解决方案。
实现降级接口
代码语言:javascript复制@Component
public class PaymentHystrixServiceImpl implements PaymentHystrixService{
@Override
public String paymentInfo_TimeOut(Integer id) {
return "不要迷恋哥,哥只是传说";
}
}
FeinClient指定降级类
代码语言:javascript复制@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentHystrixServiceImpl.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
熔断模式–>断路器
上面服务降级在启动加的@EnableCircuitBreaker就是来开启断路器的。
断路器打开条件
- 官网: https://cloud.spring.io/spring-cloud-netflix/2.2.x/reference/html/#circuit-breaker-spring-cloud-circuit-breaker-with-hystrix
A service failure in the lower level of services can cause cascading failure all the way up to the user. When calls to a particular service exceed circuitBreaker.requestVolumeThreshold
(default: 20 requests) and the failure percentage is greater than circuitBreaker.errorThresholdPercentage
(default: >50%) in a rolling window defined by metrics.rollingStats.timeInMilliseconds
(default: 10 seconds), the circuit opens and the call is not made. In cases of error and an open circuit, a fallback can be provided by the developer. --摘自官方
原文翻译之后,总结打开关闭的条件:
- 1、 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
- 2、 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
- 3、 到达以上阀值,断路器将会开启
- 4、 当开启的时候,所有请求都不会进行转发
- 5、 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5。
Hystrix对于断路器的实现
自然是还是通过@Hystrixcommond注解的属性对于上述所需条件进行控制。
舱壁模式–>资源隔离
Hystrix实现了线程隔离和信号量隔离两种策略。
线程池隔离 | 信号量隔离 | |
---|---|---|
线程 | 与调用线程非相同线程 | 与调用线程相同 |
开销 | 排队、调度、上下文开销等 | 无线程切换,开销低 |
异步 | 可以是异步,也可以是同步。看调用的方法 | 同步调用,不支持异步 |
并发支持 | 支持(最大线程池大小hystrix.threadpool.default.maximumSize) | 支持(最大信号量上限maxConcurrentRequests) |
是否超时 | 支持,可直接返回 | 不支持,如果阻塞,只能通过调用协议(如:socket超时才能返回) |
是否支持熔断 | 支持,当线程池到达maxSize后,再请求会触发fallback接口进行熔断 | 支持,当信号量达到maxConcurrentRequests后。再请求会触发fallback |
隔离原理 | 每个服务单独用线程池 | 通过信号量的计数器 |
资源开销 | 大,大量线程的上下文切换,容易造成机器负载高 | 小,只是个计数器 |
所以当请求的服务网络开销比较大的时候,或者是请求比较耗时的时候,我们最好是使用线程隔离策略,这样的话,可以保证大量的容器(tomcat)线程可用,不会由于服务原因,一直处于阻塞或等待状态,快速失败返回。而当我们请求缓存这些服务的时候,我们可以使用信号量隔离策略,因为这类服务的返回通常会非常的快,不会占用容器线程太长时间,而且也减少了线程切换的一些开销,提高了缓存服务的效率。