目录导航
前言
前面的章节我们讲了Spring Cloud 负载均衡并实现了客户端的负载均衡。
本节,继续微服务专题的内容分享,共计16小节,分别是:
- 微服务专题01-Spring Application
- 微服务专题02-Spring Web MVC 视图技术
- 微服务专题03-REST
- 微服务专题04-Spring WebFlux 原理
- 微服务专题05-Spring WebFlux 运用
- 微服务专题06-云原生应用(Cloud Native Applications)
- 微服务专题07-Spring Cloud 配置管理
- 微服务专题08-Spring Cloud 服务发现
- 微服务专题09-Spring Cloud 负载均衡
- 微服务专题10-Spring Cloud 服务熔断
- 微服务专题11-Spring Cloud 服务调用
- 微服务专题12-Spring Cloud Gateway
- 微服务专题13-Spring Cloud Stream (上)
- 微服务专题14-Spring Cloud Bus
- 微服务专题15-Spring Cloud Stream 实现
- 微服务专题16-Spring Cloud 整体回顾
本节内容重点为:
- Spring Cloud Hystrix:作为服务端服务短路实现,介绍 Spring Cloud Hystrix 常用限流的功能,同时,说明健康指标以及数据指标在生产环境下的现实意义
- 生产准备特性:介绍聚合数据指标 Turbine 、Turbine Stream,以及整合 Hystrix Dashboard
熔断机制
Netflix Hystrix 官方 wiki
一般在分布式系统里,客户端(client)发起远程调用至服务端(Service)的过程中,如果客户端有很多的时候,每个服务器的并发承受量是有限的,当超过访问上限以后,其他的客户端的响应可能反应变慢甚至失效。这就是所谓的熔断,通常有两种实现方式。
- 通过超时时间控制
假设在Service服务端通过dubbo/http提供的线程池数量为200个,理论上可以承受的并发就是200,若是client的QPS超过200,其他客户端便会陷入等待状态,直至线程池里有空闲线程。
这里的问题是:客户端的请求时间是不稳定的,有的客户端请求时间短,有的请求时间较长。而服务端若一直等待请求较慢的客户端显然是不合理的。
如果此时设置超时时间,比如为200ms,不论是否请求成功,放弃超时的client,让处于排队状态的客户端进入队列,则会极大的缓解“交通拥堵”。
Q:通过超时时间机制容错后,服务端通常会返回什么?
A:关于容错返回值的问题:通常可以返回null,或者是空对象,也可以返回异常。
QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
TPS:是TransactionsPerSecond的缩写,也就是事务数/秒。它是软件测试结果的测量单位。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。
- 通过QPS计数形式控制
通常在分布式场景下,每个通过控制每个服务端线程池的配置来平衡客户端的QPS数量以达到目的。
Spring Cloud Hystrix Client
实验内容为:通过设定的超时时间如果大于100ms,则进行服务熔断。
我们首先引用Hystrix的配置实现服务熔断。
启动类加入注解:
@EnableHystrix // 激活 Hystrix
核心实现:
@HystrixCommand(
fallbackMethod = "errorContent",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
value = "100")
}
)
@GetMapping("/say")
public String say(@RequestParam String message) throws InterruptedException {
// 如果随机时间 大于 100 ,那么触发容错
int value = random.nextInt(200);
System.out.println("say() costs " + value + " ms.");
// > 100
Thread.sleep(value);
System.out.println("ServerController 接收到消息 - say : " + message);
return "Hello, " + message;
}
public String errorContent(String message) {
return "Fault";
}
实现结果测试:
访问地址:http://localhost:9090/say?message=test
此时访问显示未超过100ms,处理程序,打印信息。
多次点击以后发现,如果访问超过100ms,则会熔断,不会打印之后的内容:
下图为对应的请求所打印的数据:
这里的原理是怎么实现的呢?请继续往下看。
熔断不仅局限于 Hystrix, 我们自己是否可以实现这样的方法实现熔断?
方法签名
访问限定符
方法返回类型
方法名称
方法参数
- 方法数量
- 方法类型+顺序
方法名称(编译时预留,IDE,Debug)
手写实现服务熔断(Future)
低级版本(无容错实现)
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
/**
* 简易版本
*
* @param message
* @return
* @throws InterruptedException
*/
@GetMapping("/say2")
public String say2(@RequestParam String message) throws Exception {
Future<String> future = executorService.submit(() -> {
return doSay2(message);
});
// 100 毫秒超时
String returnValue = future.get(100, TimeUnit.MILLISECONDS);
return returnValue;
}
实验结果测试:
访问地址:http://localhost:9090/say2?message=test
这种实现方式的问题就在于没有容错,即超过100ms并未做任何拦截,程序依然执行。
低级版本+(带容错实现)
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
/**
* 简易版本
*
* @param message
* @return
* @throws InterruptedException
*/
@GetMapping("/say2")
public String say2(@RequestParam String message) throws Exception {
Future<String> future = executorService.submit(() -> {
return doSay2(message);
});
// 100 毫秒超时
String returnValue = null;
try {
returnValue = future.get(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// 超级容错 = 执行错误 或 超时
returnValue = errorContent(message);
}
return returnValue;
}
实验结果测试:
访问地址:http://localhost:9090/say2?message=test
大于100ms:
小于100ms:
中级版本
使用aop切面解决问题:
- 首先在启动类声明aop:
@EnableAspectJAutoProxy(proxyTargetClass = true) // 激活 AOP
- Web MVC 配置:
/**
* Web MVC 配置
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CircuitBreakerHandlerInterceptor());
}
}
- 中级版本
/**
* 中级版本
*
* @param message
* @return
* @throws InterruptedException
*/
@GetMapping("/middle/say")
public String middleSay(@RequestParam String message) throws Exception {
Future<String> future = executorService.submit(() -> {
return doSay2(message);
});
// 100 毫秒超时
String returnValue = null;
try {
returnValue = future.get(100, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true); // 取消执行
throw e;
}
return returnValue;
}
- 捕获超时异常。
@RestControllerAdvice(assignableTypes = ServerController.class)
public class CircuitBreakerControllerAdvice {
@ExceptionHandler
public void onTimeoutException(TimeoutException timeoutException,
Writer writer) throws IOException {
writer.write(errorContent("")); // 网络 I/O 被容器
writer.flush();
writer.close();
}
public String errorContent(String message) {
return "Fault";
}
}
实验结果测试:
访问地址:http://localhost:9090/middle/say?message=test
大于100ms:
小于100ms:
高级版本(无注解实现)
- 接口声明
/**
* 高级版本
*
* @param message
* @return
* @throws InterruptedException
*/
@GetMapping("/advanced/say")
public String advancedSay(@RequestParam String message) throws Exception {
return doSay2(message);
}
- ServerControllerAspect
@Aspect
@Component
public class ServerControllerAspect {
private ExecutorService executorService = newFixedThreadPool(20);
@Around("execution(* com.test.micro.services.spring.cloud." +
"server.controller.ServerController.advancedSay(..)) && args(message) ")
public Object advancedSayInTimeout(ProceedingJoinPoint point, String message) throws Throwable {
Future<Object> future = executorService.submit(() -> {
Object returnValue = null;
try {
returnValue = point.proceed(new Object[]{message});
} catch (Throwable ex) {
}
return returnValue;
});
Object returnValue = null;
try {
returnValue = future.get(100, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true); // 取消执行
returnValue = errorContent("");
}
return returnValue;
}
public String errorContent(String message) {
return "Fault";
}
@PreDestroy
public void destroy() {
executorService.shutdown();
}
}
实验结果测试:
http://localhost:9090/advanced/say?message=test
大于100ms:
小于100ms:
高级版本(带注解实现)
- TimeoutCircuitBreaker 注解
@Target(ElementType.METHOD) // 标注在方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保存注解信息
@Documented
public @interface TimeoutCircuitBreaker {
/**
* 超时时间
* @return 设置超时时间
*/
long timeout();
}
- Aspect 注解实现
@Around("execution(* com.test.micro.services.spring.cloud." +
"server.controller.ServerController.advancedSay2(..)) && args(message) && @annotation(circuitBreaker)")
public Object advancedSay2InTimeout(ProceedingJoinPoint point,
String message,
CircuitBreaker circuitBreaker) throws Throwable {
long timeout = circuitBreaker.timeout();
return doInvoke(point, message, timeout);
}
- 反射API 实现
@Around("execution(* com.test.micro.services.spring.cloud." +
"server.controller.ServerController.advancedSay2(..)) && args(message) ")
public Object advancedSay2InTimeout(ProceedingJoinPoint point,
String message) throws Throwable {
long timeout = -1;
if (point instanceof MethodInvocationProceedingJoinPoint) {
MethodInvocationProceedingJoinPoint methodPoint = (MethodInvocationProceedingJoinPoint) point;
MethodSignature signature = (MethodSignature) methodPoint.getSignature();
Method method = signature.getMethod();
CircuitBreaker circuitBreaker = method.getAnnotation(CircuitBreaker.class);
timeout = circuitBreaker.timeout();
}
return doInvoke(point, message, timeout);
}
实验结果测试:
http://localhost:9090/advanced/say2?message=test
大于100ms:
小于100ms:
高级版本(信号灯实现 = 单机版限流方案)
@Target(ElementType.METHOD) // 标注在方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保存注解信息
@Documented
public @interface SemaphoreCircuitBreaker {
/**
* 信号量
*
* @return 设置超时时间
*/
int value();
}
@Around("execution(* com.test.micro.services.spring.cloud." +
"server.controller.ServerController.advancedSay3(..))" +
" && args(message)" +
" && @annotation(circuitBreaker) ")
public Object advancedSay3InSemaphore(ProceedingJoinPoint point,
String message,
SemaphoreCircuitBreaker circuitBreaker) throws Throwable {
int value = circuitBreaker.value();
if (semaphore == null) {
semaphore = new Semaphore(value);
}
Object returnValue = null;
try {
if (semaphore.tryAcquire()) {
returnValue = point.proceed(new Object[]{message});
Thread.sleep(1000);
} else {
returnValue = errorContent("");
}
} finally {
semaphore.release();
}
return returnValue;
}
实验结果测试:
http://localhost:9090/advanced/say3?message=test
大于100ms:
通过加入信号量,实际上这里会延迟一会,即所谓的熔断,然后进行处理。
小于100ms:
后记
本节代码地址:Hystrix
更多架构知识,欢迎关注本套Java系列文章:Java架构师成长之路