我们这里使用 Spring Cloud Circuit Breaker 框架 支持的 Hystrix进行服务容错。
引入Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在引入其他所需依赖如 eureka、web、test等
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 略...... -->
在启动类上使用 @SpringCloudApplication 注解
@SpringCloudApplication 注解定义如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication//spring boot 启动
@EnableDiscoveryClient//启动服务发现
@EnableCircuitBreaker//启动断路器
public @interface SpringCloudApplication {
}
在编写代码前首先我们来看一下 Hystrix 的核心类
HystrixCommand 类是一个抽象类值包含了一个抽象方法
protected abstract R run() throws Exception;
该方法是留给开发人员实现服务容错所需处理的业务逻辑的。
还有另外一个核心方法 getFallback(),作为服务回退函数的实现
protected R getFallback() {
throw new UnsupportedOperationException("No fallback available.");
}
Hystrix 组件在支持以下两种隔离方式
线程池隔离
信号量隔离
我们使用线程池隔离来编码
public class GetUserCommand extends HystrixCommand<User> {
//远程调用 user-service 的客户端工具类
@Autowired
private UserServiceClient userServiceClient;
public GetUserCommand() {
super(Setter.withGroupKey(
//设置命令组
HystrixCommandGroupKey.Factory.asKey("springHealthGroup"))
//设置命令键
.andCommandKey(HystrixCommandKey.Factory.asKey("interventionKey"))
//设置线程池键
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("aaa"))
//设置命令属性
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(10)//至少有10个请求,熔断器才进行错误率的计算
.withCircuitBreakerSleepWindowInMilliseconds(5000)//熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
.withCircuitBreakerErrorThresholdPercentage(50)//错误率达到50开启熔断保护
.withExecutionTimeoutEnabled(true)//开启执行超时
.withExecutionTimeoutInMilliseconds(5000))//超时时间
//设置线程池属性
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withMaxQueueSize(10)//最大队列
.withCoreSize(2))
);
System.out.println("GetUserCommand 启动");
}
//执行的业务方法
@Override
protected User run() {
User user = userServiceClient.getUser();
return user;
}
//服务回退方法
@Override
protected User getFallback() {
return new User(1L, "user1", "fail");
}
}
使用方式
User user = new GetUserCommand().execute();
上面的代码我们使用起来过于繁琐,Hystrix 为我们提供了一个 @HystrixCommand 注解,我们来看一下它的定义
public @interface HystrixCommand {
//命令组
String groupKey() default "";
//命令键
String commandKey() default "";
//设置线程池键
String threadPoolKey() default "";
//回退方法
String fallbackMethod() default "";
//设置命令属性
HystrixProperty[] commandProperties() default {};
//设置线程池属性
HystrixProperty[] threadPoolProperties() default {};
Class<? extends Throwable>[] ignoreExceptions() default {};
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
HystrixException[] raiseHystrixExceptions() default {};
String defaultFallback() default "";
}
我们对上面的代码重构
@HystrixCommand
public User getUser() {
......
}
也可以对部分参数进行精确的控制
@HystrixCommand(threadPoolKey = "springHealthGroup",
threadPoolProperties =
{
@HystrixProperty(name="coreSize",value="2"),//设置核心线程
@HystrixProperty(name="maxQueueSize",value="10")//设置最大队列
}
)
我们知道熔断器有三个状态,其中打开和半打开状态会导致触发熔断机制。
我们在生产者项目 user-service 在接口中添加 Threadl.sleep(5000) 即可模拟请求超时的结果
我们可以看到控制台有如下错误
{
"timestamp":"1601881721343",
"status":500,
"error":"Internal Server Error",
"exception":"com.netflix.hystrix.exception.HystrixRuntimeException",
"message":"generate Intervention time-out and fallback failed.",
"path":"/interventions/springhealth_user1/device_blood"
}
在这里,我们发现 HTTP 响应状态为 500,而抛出的异常为 HystrixRuntimeException,从异常信息上可以看出引起该异常的原因是超时。事实上,默认情况下,添加了 @HystrixCommand 注解的方法调用超过了 1000 毫秒就会触发超时异常,显然上例中设置的 2000 毫秒满足触发条件。
和设置线程池属性一样,在 HystrixCommand 中我们也可以对熔断的超时时间、失败率等各项阈值进行设置。例如我们可以在 getDevice() 方法上添加如下配置项以改变 Hystrix 的默认行为:
@HystrixCommand(
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
private DeviceMapper getDevice(String deviceCode)
所有项还可以如下配置
@HystrixCommand(
groupKey = "UserServiceClient-1",
commandKey = "getUser-1",
threadPoolKey = "user-1",
threadPoolProperties =
{
@HystrixProperty(name = "coreSize", value = "2"),
@HystrixProperty(name = "maxQueueSize", value = "10")
},
commandProperties = {
//熔断超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "12000"),
//一个滑动窗口内最小的请求数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),
//错误比率阈值
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "75"),
//触发熔断的时间值
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "7000"),
//一个滑动窗口的时间长度
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "15000"),
//一个滑动窗口被划分的数量
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "5")},
fallbackMethod = "getUserFallback"
)
Hystrix 在服务调用失败时都可以执行服务回退逻辑。在开发过程上,我们只需要提供一个 Fallback 方法实现并进行配置即可。例如,在 SpringHealth 案例系统中,对于 intervention-service 中访问 user-service 和 device-service 这两个远程调用场景,我们都可以实现 Fallback 方法。回退方法的实现也非常方便,唯一需要注意的就是 Fallback 方法的参数和返回值必须与真实的方法完全一致。如下所示的就是 Fallback 方法的一个示例:
//示例
private UserMapper getUserFallback(String userName) {
UserMapper fallbackUser = new UserMapper(0L,"no_user","not_existed_user");
return fallbackUser;
}
} 我们通过构建一个不存在的 User 信息来返回 Fallback 结果。有了这个 Fallback 方法,剩下来要做的就是在 @HystrixCommand 注解中设置“fallbackMethod”配置项。重构后的 getUser 方法如下所示:
@HystrixCommand(threadPoolKey = "springHealthGroup",
threadPoolProperties ={
@HystrixProperty(name="coreSize",value="2"),
@HystrixProperty(name="maxQueueSize",value="10")
},
fallbackMethod = "getUserFallback"
)
private UserMapper getUser(String userName) {
return userClient.getUserByUserName(userName);
}
feign:
hystrix:
# 开启熔断支持
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
# 熔断器超时时间
timeoutInMilliseconds: 600000
ribbon:
# 处理请求的超时时间,默认为1000ms = 1s ,这里需要参考熔断器超时时间
ReadTimeout: 20000
# 建立连接的时长默认1000ms
ConnectTimeout: 10000
# 同一台实例的最大重试次数,但是不包括首次调用,默认为1次
MaxAutoRetries: 1
# 重试负载均衡其他实例的最大重试次数,不包括首次调用,默认为0次
MaxAutoRetriesNextServer: 0
# 是否对所有操作都重试,默认false
OkToRetryOnAllOperations: false
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。