为什么需要容错限流
• 复杂分布式系统通常有很多依赖,如果一个应用不能对来自依赖 故障进行隔离,那么应用本身就处在被拖垮的风险中。在一个高 流量的网站中,某个单一后端一旦发生延迟,将会在数秒内导致 所有应用资源被耗尽
• 一颗老鼠屎坏了一锅汤
• 微服务需要容错限流!!!
服务雪崩
容错模式
HystrixCircuitBreaker状态变迁
-
初始时,断路器处于
CLOSED
状态,链路处于健康状态。当满足如下条件,断路器从CLOSED
变成OPEN
状态:- 周期( 可配,
HystrixCommandProperties.default_metricsRollingStatisticalWindow = 10000 ms
)内,总请求数超过一定量( 可配,HystrixCommandProperties.circuitBreakerRequestVolumeThreshold = 20
) 。 - 错误请求占总请求数超过一定比例( 可配,
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage = 50%
) 。
- 周期( 可配,
-
断路器处于
OPEN
状态,命令执行时,若当前时间超过断路器开启时间一定时间(HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds = 5000 ms
),断路器变成HALF_OPEN
状态,尝试调用正常逻辑,根据执行是否成功,打开或关闭熔断器。
容错框架
断路器Hystrix
Netflix创建了一个名为Hystrix的库,它实现了断路器模式。在微服务体系结构中,有多个服务调用层是很常见的。引用官网的图如下:
这段话描述了断路器在什么情况下会打开,这里设计到断路器的三个重要参数:
l 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
l 请求总数下限:在快照时间窗内,必须满足请求总数下限才有资格根据熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用此时不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
l 错误百分比下限:当请求总数在快照时间窗内超过了下限,比如发生了30次调用,如果在这30次调用中,有16次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%下限情况下,这时候就会将断路器打开。
那么当断路器打开之后会发生什么呢?
断路器未打开之前,每个请求都会在当hystrix超时之后返回Fallback,每个请求时间延迟就是近似hystrix的超时时间,如果设置为5秒,每个请求就都要延迟5秒才会返回。当熔断器在10秒内发现请求总数超过20,并且错误百分比超过50%,这个时候熔断器打开,打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级逻辑,这个时候就不会再等待5秒之后才返回Fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少相应延迟的效果。
在断路器打开之后,处理逻辑并没有结束,降级逻辑已经被当成了主逻辑,那么原来的主逻辑要如何恢复呢?对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新记时。
通过上面的一系列机制,hystrix的断路器实现了对依赖资源故障的端口、对降级策略的自动切换以及对主逻辑的自动恢复机制。这使得我们的微服务在依赖外部服务或资源的时候得到了非常好的保护,同时对于一些具备降级逻辑的业务需求可以实现自动化的切换与恢复,相比于设置开关由监控和运维来进行切换的传统实现方式显得更为智能和高效。
hystrix原理
Hystrix主要概念
Hystrix主要配置项
Hystrix dashboard
内部机制
源码剖析
内部配置
hystrix:
command:
default:
execution:
isolation:
thread:
# 超时时间,默认1000ms
timeoutInMilliseconds: 1000
circuitBreaker:
#熔断器在整个统计时间内是否开启的阀值,默认20。也就是10秒钟内至少请求20次,熔断器才发挥起作用
requestVolumeThreshold: 10
#熔断器默认工作时间,默认:5秒.熔断器中断请求5秒后会关闭重试,如果请求仍然失败,继续打开熔断器5秒,如此循环
sleepWindowInMilliseconds: 10000
#当出错率超过50%后熔断器启动
errorThresholdPercentage: 10
- 抛出ServiceException触发熔断
package com.open.capacity.user.query.service.impl;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import com.open.capacity.user.query.dao.StopUserQueryMapper;
import com.open.capacity.user.query.dto.ListStopUserQueryResponse;
import com.open.capacity.user.query.service.StopUserQueryService;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class StopUserQueryServiceImpl implements StopUserQueryService{
public final StopUserQueryMapper stopUserQueryMapper;
@Transactional
public ListStopUserQueryResponse stopUserQuery(Map<String, Object> params) {
try {
throw new RuntimeException("");
} catch (Exception e) {
throw new HystrixBadRequestException(e.getMessage());
}
}
}
package com.open.capacity.user.query.controller;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.commons.collections4.MapUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import com.open.capacity.common.exception.controller.ControllerException;
import com.open.capacity.common.web.dto.BaseResponse;
import com.open.capacity.common.web.dto.ResultCode;
import com.open.capacity.user.query.service.StopUserQueryService;
@RestController
@RequestMapping("/stopUser")
public class StopUserQueryController {
@Resource
private StopUserQueryService stopUserQueryService;
@Resource
private ObjectMapper objectMapper ;
@GetMapping("/query")
@HystrixCommand(fallbackMethod="fallback",
commandProperties = {
//信号量隔离
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
//信号量最大并发度
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "1000")
})
public BaseResponse commandQuery(@RequestParam Map<String, Object> params) {
//设置分页信息,分别是当前页数和每页显示的总记录数【记住:必须在mapper接口中的方法执行之前设置该分页信息】
if (MapUtils.getInteger(params, "currentPage") ==null){
throw new ControllerException("请输入currentPage参数") ;
}
if (MapUtils.getInteger(params, "pageSize") ==null){
throw new ControllerException("请输入pageSize参数") ;
}
return stopUserQueryService.stopUserQuery(params);
}
/**
* fallback返回拖底数据
* 以下四种情况将触发getFallback调用
* 1、方法抛出非HystrixBadRequestException异常。
* 2、方法调用超时
* 3、熔断器开启拦截调用
* 4、线程池/队列/信号量是否跑满
* @return
*/
public BaseResponse fallback(@RequestParam Map<String, Object> params){
return BaseResponse
.builder()
.code(ResultCode.INTERNAL_SERVER_ERROR.getCode())
.message("查询失败,触发熔断")
.build();
}
}
抛出HystrixBadRequestException不触发熔断
package com.open.capacity.user.query.service.impl;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import com.open.capacity.user.query.dao.StopUserQueryMapper;
import com.open.capacity.user.query.dto.ListStopUserQueryResponse;
import com.open.capacity.user.query.service.StopUserQueryService;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class StopUserQueryServiceImpl implements StopUserQueryService{
public final StopUserQueryMapper stopUserQueryMapper;
@Transactional
public ListStopUserQueryResponse stopUserQuery(Map<String, Object> params) {
try {
throw new RuntimeException("");
} catch (Exception e) {
throw new HystrixBadRequestException(e.getMessage());
}
}
}
package com.open.capacity.user.query.controller;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.commons.collections4.MapUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import com.open.capacity.common.exception.controller.ControllerException;
import com.open.capacity.common.web.dto.BaseResponse;
import com.open.capacity.common.web.dto.ResultCode;
import com.open.capacity.user.query.service.StopUserQueryService;
@RestController
@RequestMapping("/stopUser")
public class StopUserQueryController {
@Resource
private StopUserQueryService stopUserQueryService;
@Resource
private ObjectMapper objectMapper ;
@GetMapping("/query")
@HystrixCommand(fallbackMethod="fallback",
commandProperties = {
//信号量隔离
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
//信号量最大并发度
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "1000")
})
public BaseResponse commandQuery(@RequestParam Map<String, Object> params) {
//设置分页信息,分别是当前页数和每页显示的总记录数【记住:必须在mapper接口中的方法执行之前设置该分页信息】
if (MapUtils.getInteger(params, "currentPage") ==null){
throw new ControllerException("请输入currentPage参数") ;
}
if (MapUtils.getInteger(params, "pageSize") ==null){
throw new ControllerException("请输入pageSize参数") ;
}
return stopUserQueryService.stopUserQuery(params);
}
/**
* fallback返回拖底数据
* 以下四种情况将触发getFallback调用
* 1、方法抛出非HystrixBadRequestException异常。
* 2、方法调用超时
* 3、熔断器开启拦截调用
* 4、线程池/队列/信号量是否跑满
* @return
*/
public BaseResponse fallback(@RequestParam Map<String, Object> params){
return BaseResponse
.builder()
.code(ResultCode.INTERNAL_SERVER_ERROR.getCode())
.message("查询失败,触发熔断")
.build();
}
}
Feign、Ribbon、Hystrix超时和重试
#设置最大容错超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 16000
##feign参数优化
feign:
client:
config:
default:
loggerLevel: full ## 配合logging.level=trace debug用于开发调式日志
compression:
request:
enabled: true
mine-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
#设置最大超时时间
ribbon:
eager-load:
enabled: true
ServerListRefreshInterval: 10 #刷新服务列表源的间隔时间
OkToRetryOnAllOperations: true
### 因为ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制 对所有的操作请求都进行重试,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同
MaxAutoRetries: 1
### 同一台实例最大重试次数,不包括首次调用,默认0
MaxAutoRetriesNextServer: 1
### 重试负载均衡其他的实例最大重试次数,不包括首次调用,默认1
ReadTimeout: 16000
## 指的是建立连接后从服务器读取到可用资源所用的时间, 默认为1秒 超过1秒没有读取到内容时,就认为此次读取不到内容并抛出Java.net.SocketException: read time out的异常
ConnectTimeout: 16000 ## # ribbon请求连接的超时时间,默认值2000# 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间