Hystrix断路器介绍
复杂的链路结构调用,在其中一个服务出现问题时,会导致与其协调的服务运行压力越来越大,引起雪崩效应。
Hystrix通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应,而不是长时间等待或抛出异常。避免故障蔓延。
- 降级:服务不可用时,向调用方返回一个符合预期的,可处理的的备选响应(fallback),运行异常、超时、熔断、线程池满等会触发降级。
- 熔断:达到最大访问量,拒绝访问,调用服务降级方法返回。
- 限流:高并发操作时,有序进行。
1、模拟超时
1.1 支付模块模拟高并发访问(Eureka服务注册);
PaymentController .java
@RestController
@RequestMapping("/payment")
@Slf4j
@RequiredArgsConstructor
public class PaymentController {
private final PaymentService paymentService ;
@GetMapping("/hy/ok/{id}")
public String getOk(@PathVariable Integer id){
return paymentService.getOk(id);
}
@GetMapping("/hy/timeout/{id}")
public String getTimeOut(@PathVariable Integer id){
return paymentService.getTimeOut(id);
}
}
PaymentService .java
public interface PaymentService extends IService<Payment> {
public String getOk(Integer id);
public String getTimeOut(Integer id);
}
PaymentServiceImpl .java
@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, Payment> implements PaymentService {
@Override
public String getOk(Integer id) {
return Thread.currentThread().getName()+"、ok:"+id;
}
@Override
public String getTimeOut(Integer id) {
int time = 3000;
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Thread.currentThread().getName()+"、ok:"+id + "耗时:"+ time;
}
}
访问
采坑8:@PathVariable(“id”)使用过程中需带上参数,不然可能报错;
2022-03-12 22:02:17.580 ERROR 24720 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'paymentController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.payment.FeignService.PaymentFeignService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: PathVariable annotation was empty on param 0.
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:337) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at com.payment.Consumer4Application.main(Consumer4Application.java:12) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.2.2.RELEASE.jar:2.2.2.RELEASE]
1.2 订单消费者模块模拟高并发访问(openfeign服务调用);
PaymentController .java
package com.payment.Controller;
import com.commons.Entity.Payment;
import com.commons.Entity.Result;
import com.payment.FeignService.PaymentFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/consumer4")
public class PaymentController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/hy/ok/{id}")
public String getOk(@PathVariable("id") Integer id){
return paymentFeignService.getOk(id);
}
@GetMapping("/hy/timeout/{id}")
public String getTimeOut(@PathVariable("id") Integer id){
return paymentFeignService.getTimeOut(id);
}
}
PaymentFeignService.java
package com.payment.FeignService;
import com.commons.Entity.Payment;
import com.commons.Entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "PAYMENT") //值为注册的支付模块微服务application.name
@Component
public interface PaymentFeignService {
@GetMapping("/payment/hy/ok/{id}")
public String getOk(@PathVariable("id") Integer id);
@GetMapping("/payment/hy/timeout/{id}")
public String getTimeOut(@PathVariable("id") Integer id);
}
- 主程序:@EnableFeignClients //激活
@SpringBootApplication(scanBasePackages = "com.payment")
@EnableFeignClients //激活
public class Consumer4Application {
public static void main(String[] args) {
SpringApplication.run(Consumer4Application.class, args);
}
}
- application.yml
采坑9:需要开启超时控制,否则3秒会返回超时异常
2022-03-12 21:56:11.353 ERROR 25492 --- [nio-8085-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.RetryableException: Read timed out executing GET http://PAYMENT/payment/hy/timeout/1] with root cause
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.8.0_181]
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) ~[na:1.8.0_181]
at java.net.SocketInputStream.read(SocketInputStream.java:171) ~[na:1.8.0_181]
at java.net.SocketInputStream.read(SocketInputStream.java:141) ~[na:1.8.0_181]
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) ~[na:1.8.0_181]
at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) ~[na:1.8.0_181]
at java.io.BufferedInputStream.read(BufferedInputStream.java:345) ~[na:1.8.0_181]
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:735) ~[na:1.8.0_181]
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:678) ~[na:1.8.0_181]
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1587) ~[na:1.8.0_181]
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492) ~[na:1.8.0_181]
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480) ~[na:1.8.0_181]
at feign.Client$Default.convertResponse(Client.java:82) ~[feign-core-10.4.0.jar:na]
at feign.Client$Default.execute(Client.java:78) ~[feign-core-10.4.0.jar:na]
at org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.execute(FeignLoadBalancer.java:93) ~[spring-cloud-openfeign-core-2.2.1.RELEASE.jar:2.2.1.RELEASE]
at org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.execute(FeignLoadBalancer.java:56) ~[spring-cloud-openfeign-core-2.2.1.RELEASE.jar:2.2.1.RELEASE]
at com.netflix.client.AbstractLoadBalancerAwareClient$1.call(AbstractLoadBalancerAwareClient.java:104) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:303) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:287) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:231) ~[rxjava-1.3.8.jar:1.3.8]
at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:228) ~[rxjava-1.3.8.jar:1.3.8]
server:
port: 8085
eureka:
client:
register-with-eureka: false #是否要注册
fetchRegistry: true #是否抓取注册信息
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001:7001/eureka #,http://eureka7002:7002/eureka
spring:
application:
name : consumer8085
datasource:
url: jdbc:mysql://localhost:3306/springboot-mybatisplus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
username: root
password: 123456
driver-class-name=com: mysql.cj.jdbc.Driver
#2、mybatis-plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config :
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath:mapper/*.xml
logging:
level:
com.payment.FeignService: debug
feign:
client:
config:
default:
#简历连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
ConnectTimeOut: 5000
#指建立连接后从服务端读取到可用资源所用的时间
ReadTimeOut: 10000
访问:
此时如果在高并发情况下访问,就会造成服务延迟等待,会出现超时错误,此时就需要服务降级。
2、服务降级
2.1 降级配置
@HystrixCommand设置自身调用超时时间峰值,峰值内正常运行,超过峰值,作为服务降级fallback;
- 2.1.1payment8001服务提供者-支付模块
pom.xml依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
PaymentServiceImpl 设置@HystrixCommand
@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentMapper, Payment> implements PaymentService {
@Override
@Transactional
public boolean saveEntity(Payment payment) {
baseMapper.insert(payment);
return true;
}
@Override
public Payment getPayment(Integer id) {
Payment payment = baseMapper.selectById(id);
return payment;
}
@Override
public String getOk(Integer id) {
return Thread.currentThread().getName()+"、ok:"+id;
}
@HystrixCommand(fallbackMethod = "TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
}) //如果该方法执行异常,执行TimeOutHandler方法
@Override
public String getTimeOut(Integer id) {
int time = 5000;
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Thread.currentThread().getName()+"、ok:"+id + "耗时:"+ time;
}
public String TimeOutHandler(Integer id){
return Thread.currentThread().getName()+"执行异常";
}
}
主启动类:@EnableCircuitBreaker
@SpringBootApplication(scanBasePackages = "com.payment")
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
采坑10:fallbackMethod方法参数一定要跟本方法一致
There was an unexpected error (type=Internal Server Error, status=500).
fallback method wasn't found: TimeOutHandler([class java.lang.Integer])
com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException: fallback method wasn't found: TimeOutHandler([class java.lang.Integer])
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider$FallbackMethodFinder.doFind(MethodProvider.java:190)
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider$FallbackMethodFinder.find(MethodProvider.java:159)
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider.getFallbackMethod(MethodProvider.java:73)
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider.getFallbackMethod(MethodProvider.java:59)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect.setFallbackMethod(HystrixCommandAspect.java:331)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect.access$300(HystrixCommandAspect.java:64)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect$MetaHolderFactory.metaHolderBuilder(HystrixCommandAspect.java:176)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect$CommandMetaHolderFactory.create(HystrixCommandAspect.java:258)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect$MetaHolderFactory.create(HystrixCommandAspect.java:166)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect.methodsAnnotatedWithHystrixCommand(HystrixCommandAspect.java:93)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at com.payment.Service.impl.PaymentServiceImpl$$EnhancerBySpringCGLIB$$5a388c27.getTimeOut(<generated>)
at com.payment.Controller.PaymentController.getTimeOut(PaymentController.java:51)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
访问:
除了在支付-服务提供者模块降级以外,一般推荐在订单-服务消费者模块使用降低
- 2.1.2 cunsumer服务提供者-支付模块
pom.xml:与订单模块相同
application.yml开启
feign:
hystrix:
enabled: true
主启动类:@EnableHystrix激活
@SpringBootApplication(scanBasePackages = "com.payment")
@EnableFeignClients //激活
@EnableHystrix
public class Consumer4Application {
public static void main(String[] args) {
SpringApplication.run(Consumer4Application.class, args);
}
}
PaymentController .java
@RestController
@RequestMapping("/consumer4")
public class PaymentController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/payment/{id}")
public Result getById (@PathVariable("id") Integer id){
return paymentFeignService.getEntity(id);
}
@GetMapping("/hy/ok/{id}")
public String getOk(@PathVariable("id") Integer id){
return paymentFeignService.getOk(id);
}
@HystrixCommand(fallbackMethod = "TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
}) //如果该方法执行异常,执行TimeOutHandler方法
@GetMapping("/hy/timeout/{id}")
public String getTimeOut(@PathVariable("id") Integer id){
return paymentFeignService.getTimeOut(id);
}
public String TimeOutHandler(Integer id){
return Thread.currentThread().getName()+"执行异常";
}
}
访问:
以上服务降级每个方法都需要配置@HystrixCommand降级方法,导致业务方法和逻辑方法混乱,也会导致代码膨胀。此时需要DefaultProperties。
2.2 DefaultProperties全局服务降级
@DefaultProperties加在类上面,当该类中的方法没有指定@HystrixCommand配置的fallbackMethod 时,使用该注解指定的降级方法。
采坑11:使用全局降级方法需要不带参数
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Mar 12 23:39:11 CST 2022
There was an unexpected error (type=Internal Server Error, status=500).
fallback method wasn't found: TimeOutHandler([])
com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException: fallback method wasn't found: TimeOutHandler([])
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider$FallbackMethodFinder.doFind(MethodProvider.java:190)
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider$FallbackMethodFinder.find(MethodProvider.java:159)
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider$FallbackMethodFinder.find(MethodProvider.java:161)
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider.getFallbackMethod(MethodProvider.java:73)
at com.netflix.hystrix.contrib.javanica.utils.MethodProvider.getFallbackMethod(MethodProvider.java:59)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect.setFallbackMethod(HystrixCommandAspect.java:331)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect.access$300(HystrixCommandAspect.java:64)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect$MetaHolderFactory.metaHolderBuilder(HystrixCommandAspect.java:176)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect$CommandMetaHolderFactory.create(HystrixCommandAspect.java:258)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect$MetaHolderFactory.create(HystrixCommandAspect.java:166)
at com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect.methodsAnnotatedWithHystrixCommand(HystrixCommandAspect.java:93)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
以消费者-订单模块PaymentController 位例
@RestController
@DefaultProperties(defaultFallback = "TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
}) //如果该方法执行异常,执行TimeOutHandler方法
@RequestMapping("/consumer4")
public class PaymentController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/payment/{id}")
public Result getById (@PathVariable("id") Integer id){
return paymentFeignService.getEntity(id);
}
@GetMapping("/hy/ok/{id}")
public String getOk(@PathVariable("id") Integer id){
return paymentFeignService.getOk(id);
}
@HystrixCommand
@GetMapping("/hy/timeout/{id}")
public String getTimeOut(@PathVariable("id") Integer id){
return paymentFeignService.getTimeOut(id);
}
public String TimeOutHandler(){ //不带参数
return Thread.currentThread().getName()+"执行异常";
}
}
以上操作虽然简化了配置,但是降级逻辑和业务逻辑还是混合,耦合性较高,需要FenignFallback解耦。
2.3 FenignFallback通配服务降级
定义一个类,实现之前的业务调用接口,为该接口统一异常处理。
- PaymentFeignServiceIml 实现类
package com.payment.FeignService;
import com.commons.Entity.Payment;
import com.commons.Entity.Result;
import org.springframework.stereotype.Component;
@Component
public class PaymentFeignServiceIml implements PaymentFeignService{
@Override
public Result<Payment> getEntity(Integer id) {
return null;
}
@Override
public String getOk(Integer id) {
return Thread.currentThread().getName()+"ok";
}
@Override
public String getTimeOut(Integer id) {
return Thread.currentThread().getName()+"timeout";
}
}
- PaymentFeignService 接口添加
@FeignClient(value = "PAYMENT" , fallback = PaymentFeignServiceIml.class)
//值为注册的支付模块微服务application.name: PAYMENT
//PaymentFeignServiceIml为错误处理实现类
@FeignClient(value = "PAYMENT" , fallback = PaymentFeignServiceIml.class)
@Component
public interface PaymentFeignService {
@GetMapping("/payment/{id}") //对应支付模块该接口的地址
public Result<Payment> getEntity(@PathVariable("id") Integer id);
@GetMapping("/payment/hy/ok/{id}")
public String getOk(@PathVariable("id") Integer id);
@GetMapping("/payment/hy/timeout/{id}")
public String getTimeOut(@PathVariable("id") Integer id);
}
- PaymentController 去掉原降级方法
package com.payment.Controller;
import com.commons.Entity.Result;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.payment.FeignService.PaymentFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@DefaultProperties(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
}) //如果该方法执行异常,执行TimeOutHandler方法
@RequestMapping("/consumer4")
public class PaymentController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/payment/{id}")
public Result getById (@PathVariable("id") Integer id){
return paymentFeignService.getEntity(id);
}
@GetMapping("/hy/ok/{id}")
public String getOk(@PathVariable("id") Integer id){
return paymentFeignService.getOk(id);
}
@HystrixCommand
@GetMapping("/hy/timeout/{id}")
public String getTimeOut(@PathVariable("id") Integer id){
return paymentFeignService.getTimeOut(id);
}
// public String TimeOutHandler(){
// return Thread.currentThread().getName()+"执行异常";
// }
}
- application.yml 添加注解:feign.hystrix.enabled: true
server:
port: 8085
eureka:
client:
register-with-eureka: false #是否要注册
fetchRegistry: true #是否抓取注册信息
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001:7001/eureka #,http://eureka7002:7002/eureka
#ribbon:
# ReadTimeOut: 5000 #请求连接时间
# ConnectT888imeout: 5000 #资源处理时间
spring:
application:
name : consumer8085
datasource:
url: jdbc:mysql://localhost:3306/springboot-mybatisplus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
username: root
password: 123456
driver-class-name=com: mysql.cj.jdbc.Driver
#2、mybatis-plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config :
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath:mapper/*.xml
logging:
level:
com.payment.FeignService: debug
#feign:
# client:
# config:
# default:
# #简历连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
# ConnectTimeOut: 5000
# #指建立连接后从服务端读取到可用资源所用的时间
# ReadTimeOut: 10000
feign:
hystrix:
enabled: true
目录:
访问:
3、服务熔断
当扇出链路的某个服务出错不可用或者响应时间过长,会进行服务降级,熔断该微服务节点,快速返回响应。当检测到该节点正常以后,恢复该链路。
- PaymentController
@HystrixCommand(fallbackMethod = "circuitBreaker", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "1000"),//时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败率达到多少
package com.payment.Controller;
import com.commons.Entity.Result;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.payment.FeignService.PaymentFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@DefaultProperties(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
}) //如果该方法执行异常,执行TimeOutHandler方法
@RequestMapping("/consumer4")
public class PaymentController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/payment/{id}")
public Result getById(@PathVariable("id") Integer id) {
return paymentFeignService.getEntity(id);
}
@GetMapping("/hy/ok/{id}")
public String getOk(@PathVariable("id") Integer id) {
return paymentFeignService.getOk(id);
}
@HystrixCommand
@GetMapping("/hy/timeout/{id}")
public String getTimeOut(@PathVariable("id") Integer id) {
return paymentFeignService.getTimeOut(id);
}
@HystrixCommand(fallbackMethod = "circuitBreaker", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "1000"),//时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败率达到多少
})
@GetMapping("get/hy/{id}")
public String getCircuitBreaker(@PathVariable("id") Integer id) {
if(id < 0){
throw new RuntimeException("id不能为负数");
}
return paymentFeignService.getTimeOut(id);
}
public String circuitBreaker(Integer id) {
return Thread.currentThread().getName() + "熔断";
}
// public String TimeOutHandler(){
// return Thread.currentThread().getName()+"执行异常";
// }
}
其他内容相同
- 目录
- 访问
当满足配置条件后就会熔断,但是正常之后不会立即恢复,会在正确率等条件满足条之后慢慢恢复。
熔断器有3个重要参数:
- 快照时间窗:是否熔断需要统计一些数据,统计的时间范围叫做快照时间窗,默认为最近10秒;
- 请求总数阈值:快照时间窗内,请求数达到一定阈值才有资格熔断,意味着10秒内,调用次数达到阈值数才会熔断;
- 错误百分比:在快照时间窗内,达到请求总数阈值,如在10秒内,调用30次,15次异常,达到50%错误百分比,就会熔断。
熔断器开启或者关闭条件:
- 快照时间窗内,请求次数要达到请求总数阈值,错误百分比要达到设置值,就会熔断开启;
- 一段时间后(默认5秒),处于半开状态,会让一个请求进行转发,如果成功,在熔断关闭,如果失败,继续开启,等待下一个5秒。
4、图形化Dashboard搭建
Hystrix提供了准实时的调用监控,持续记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户。
(1)监控方搭建
- 监控方依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>cloud2022</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>hystrix</groupId>
<artifactId>hystrix</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>com.commons</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
- application.yml
server:
port: 9001
spring:
application:
name : hystrix9001
datasource:
url: jdbc:mysql://localhost:3306/springboot-mybatisplus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
username: root
password: 123456
driver-class-name=com: mysql.cj.jdbc.Driver
- 主启动类:@EnableHystrixDashboard
package com.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication(scanBasePackages = "com.hystrix")
@EnableHystrixDashboard
public class HyStrixApplication {
public static void main(String[] args) {
SpringApplication.run(HyStrixApplication.class, args);
}
}
采坑11
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
Process finished with exit code 0
缺少数据库配置:https://blog.csdn.net/qq_45498460/article/details/112860809
- 目录:
- 访问:http://localhost:9001/hystrix
(2)被监控方搭建
- 被监控方依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在原来服务熔断-消费者订单模块的例子上修改主启动类,其他熔断内容不变;
- 主启动类:
package com.payment;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
@SpringBootApplication(scanBasePackages = "com.payment")
@EnableFeignClients //激活
@EnableHystrix
public class Consumer4Application {
public static void main(String[] args) {
SpringApplication.run(Consumer4Application.class, args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
- 访问:http://localhost:9001/hystrix
- 填写被监控方信息
访问监控熔断测试:
监控结果: