5.1:在项目下建Module 名字:sprint-cloud-provider-hystrix-demo8001
5.2:pom.xml添加依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.16version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.5version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
<scope>runtimescope>
dependency>
5.3:yml配置文件
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-demo
eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己,默认值都是true
fetchRegistry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务,默认值都是true
service-url:
defaultZone: http://localhost:7001/eureka # 入驻的服务注册中心地址
5.4:sprint-cloud-provider-hystrix-demo8001的启动类
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8003 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8003.class,args);
}
}
5.5:service业务类
@Service
public class DemoService {
/**能够正常访问的方法*/
public String demoSuccess(Integer id) {
return "success"
}
/**模拟出错的方法*/
public String demoErroe(Integer id) {
int time = 3;
//暂停几秒钟线程,程序本身没有错误,就是模拟超时
try {
TimeUnit.SECONDS.sleep(time);
}catch (InterruptedException e) {
e.printStackTrace();
}
return "fail"
}
}
5.6:controller类
@RestController
@Slf4j
@RequestMapping("/demo/hystrix")
public class DemoController {
@Resource
private DemoService demoService;
@GetMapping("ok/{id}")
public String demo_OK(@PathVariable("id")Integer id) {
String result = demoService.demoSuccess(id);
log.info("===> result: " + result);
return result;
}
@GetMapping("timeout/{id}")
public String demo_TimeOut(@PathVariable("id")Integer id) {
String result = demoService.demoErroe(id);
log.info("===> result: " + result);
return result;
}
}
用 Jmeter 进行高并发压力测试,用20000个请求都去访问timeout/{id}服务,我们模拟的是20000的访问量(,实际中可能会有远大于20000的访问量,当访问量更多的时候,甚至可能卡死服务,原因就是 Tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理 。
解决的问题方向
超时导致服务器变慢(转圈) => 超时不再等待
出错(宕机或程序运行出错) => 出错要有兜底
@Component
@FeignClient("cloud-provider-hystrix-demo")
public interface DemoConsumerHystrixService {
@GetMapping("/demo/hystrix/ok/{id}")
public String demoSuccess(@PathVariable("id") Integer id);
@GetMapping("/demo/hystrix/timeout/{id}")
public String demoErroe(@PathVariable("id") Integer id);
}
@RestController
@Slf4j
public class DemoHystrixController {
@Resource
private DemoConsumerHystrixService demoConsumerHystrixService ;
@GetMapping("/consumer/demo/hystrix/ok/{id}")
public String demoConsumerSuccess(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService .demoSuccess(id);
return result;
}
@GetMapping("/consumer/demo/hystrix/timeout/{id}")
public String demoConsumerErroe(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService .demoErroe(id);
return result;
}
}
- 在提供方服务的主启动类上添加 @EnableCircuitBreaker 注解对熔断器进行激活
- 降级的配置用 @HystrixCommand 注解,在服务提供方自身找问题,设置自身调用超时时间的峰值,在峰值内可以正常运行,超过了峰值需要有兜底的方法处理,用作服务降级。
- 首先在服务提供方的业务类上启用 @HystrixCommand 实现报异常后如何处理,也就是一旦调用服务方法失败并抛出了错误信息后,会自动调用 @HystrixCommand 标注好的fallbackMethod服务降级方法。在服务提供方的service中我们修改 超时服务:
@Service
public class DemoService {
/**能够正常访问的方法*/
public String demoSuccess(Integer id) {
return "success";
}
/**模拟出错的方法*/
@HystrixCommand(fallbackMethod = "demoErrorHandler",
commandProperties = {
@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds",
value = "3000")
})
public String demoerror(Integer id) {
//int age = 10/0; //模拟异常,也会兜底
int time = 5;
//暂停几秒钟线程,程序本身没有错误,就是模拟超时
try {
TimeUnit.SECONDS.sleep(time);
}catch (InterruptedException e) {
e.printStackTrace();
}
return "error";
}
/** 定制服务降级方法*/
public String demoErrorHandler(Integer id) {
return "系统忙,请稍后再试";
}
}
8.1:既然服务的提供方可以进行降级保护,那么服务的消费方,也可以更好地保护自己,也可以对自己进行降级保护,也就是说Hystrix服务降级既可以放在服务端(服务提供方),也可以放在客户端(服务消费方),但是! 通常是用客户端做服务降级 ,下面在服务消费方即客户端配置自己的服务降级保护,修改消费方的配置文件,添加如下配置已使其支持Hystrix,支持在@FeignClient配置fallback :
feign:
hystrix:
enabled: true
8.2:在消费方的主启动类上添加 @EnableHystrix 激活Hystrix服务。Controller中同样加入 @HystrixCommand 注解已实现服务降级:
@RestController
@Slf4j
public class demoHystrixController {
@Resource
private DemoConsumerHystrixService demoConsumerHystrixService ;
@GetMapping("/consumer/demo/hystrix/ok/{id}")
public String demoConsumerSuccess(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService.demoSuccess(id);
return result;
}
@GetMapping("/consumer/demo/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "demoConsumerFailHandler",
commandProperties = {
@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds",
value = "1500")
})
public String demoConsumerError(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService.demoError(id);
return result;
}
/** 定制服务降级方法*/
public String demoConsumerFailHandler(Integer id) {
return "我是消费者,系统忙,请稍后再试";
}
}
而当前的这种处理方式是有问题的,也就是每个业务方法都对应了一个服务降级方法,这会导致代码膨胀 ,所以我们应该定义一个统一的服务降级方法,统一的方法和自定义的方法分开。而且我们 将服务降级方法和业务逻辑混合在了一起,这会导致代码混乱,业务逻辑不清晰。我们可以用feign接口中的 @DefaultProperties(defaultFallback = “”) 注解来配置全局的服务降级方法,也就是说 自己配置过 @HystrixCommand(fallbackMethod = “”) fallbackMethod方法的采用自己配置的服务降级方法,而没有配置过的就采用 @DefaultProperties(defaultFallback = “”) 配置的全局的服务降级方法。 这样的话通用的服务降级方法和独享的服务降级方法分开,避免了代码膨胀,合理减少了代码量,修改服务消费方的Controller入下:
//消费端Controller
@RestController
@Slf4j
//没有特别指明就用这个统一的
@DefaultProperties(defaultFallback = "demoConsumer_Global_FailHandler")
public class DemoHystrixController {
@Resource
private DemoConsumerHystrixService demoConsumerHystrixService ;
@GetMapping("/consumer/demo/hystrix/ok/{id}")
public String demoConsumerSuccess(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService .demoSuccess(id);
return result;
}
@GetMapping("/consumer/demo/hystrix/timeout/{id}")
//特别指明使用哪一个兜底方法
// @HystrixCommand(fallbackMethod = "demoConsumerFailHandler",
// commandProperties = {
// @HystrixProperty(
// name = "execution.isolation.thread.timeoutInMilliseconds",
// value = "1500")
// })
@HystrixCommand //没有具体指明就使用全局的
public String demoConsumerError(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService .demoError(id);
return result;
}
/** 定制服务降级方法*/
public String demoConsumerFailHandler(Integer id) {
return "我是消费者,系统忙,请稍后再试";
}
/**
* 全局服务降级方法
* @return
*/
public String demoConsumer_Global_FailHandler() {
return "全局异常处理信息";
}
}
这里需要注意的是, 无论是否配置了定制服务降级方法,都要在其服务上加入注解 @HystrixCommand , 否则服务降级和该服务没关系。
客户端的接口添加一个服务降级处理的实现类即可实现解耦,我们的80客户端已经有了DemoConsumerHystrixService 接口,,新建一个类 demoConsumerFallbackService实现该接口,并重写接口中的方法,为接口里的方法进行异常处理,并且我们在 DemoConsumerHystrixService @FeignClient注解中声明其服务降级方法所在的类
@Service
//当出现错误是到demoConsumerFallbackService类中找服务降级方法
@FeignClient(value = "cloud-provider-hystrix-demo",
fallback = demoConsumerFallbackService.class)
public interface DemoConsumerHystrixService {
@GetMapping("/demo/hystrix/ok/{id}")
public String demoSuccess(@PathVariable("id") Integer id);
@GetMapping("/demo/hystrix/timeout/{id}")
public String demoError(@PathVariable("id") Integer id);
}
@Service
public class demoConsumerFallbackService implements DemoConsumerHystrixService {
@Override
public String demoSuccess(Integer id) {
return "demoConsumerFallbackService demoSuccess出现异常";
}
@Override
public String demoError(Integer id) {
return "demoConsumerFallbackService demoError出现异常";
}
}
@RestController
@Slf4j
public class demoHystrixController{
@Resource
private DemoConsumerHystrixService demoConsumerHystrixService ;
@GetMapping("/consumer/demo/hystrix/ok/{id}")
public String demoConsumerSuccess(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService.demoSuccess(id);
return result;
}
@GetMapping("/consumer/demo/hystrix/timeout/{id}")
public String demoConsumerError(@PathVariable("id") Integer id) {
String result = demoConsumerHystrixService.demoError(id);
return result;
}
}
/**
* 服务熔断
* fallbackMethod 服务降级方法
* circuitBreaker.enabled 是否开启断路器
* circuitBreaker.requestVolumeThreshold 请求次数
* circuitBreaker.sleepWindowInMilliseconds 时间窗口期
* circuitBreaker.errorThresholdPercentage 失败率达到多少后跳闸
*
* 以下配置意思是在10秒时间内请求10次,如果有6次是失败的,就触发熔断器
*
* 注解@HystrixProperty中的属性在com.netflix.hystrix.HystrixCommandProperties类中查看
*/
@HystrixCommand(fallbackMethod = "demoCircuitBreaker_fallback",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",
value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",
value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",
value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",
value = "60")
})
Execution
- execution.isolation.strategy
指示使用哪种隔离策略,有两种:thread(线程池,默认)并发请求受到线程池最大线程限制,semaphore(信号量)受到信号量计数的限制,一般使用线程的方式- execution.isolation.thread.timeoutInMilliseconds
用于设置超时的时间,单位是毫秒,默认为1s- execution.timeout.enabled
用于指示是否启用超时,默认为true- execution.isolation.thread.interruptOnTimeout
是否在服务超时后中断,默认为true,需要注意在 JVM 中我们无法强制中断一个线程,如果 Hystrix 方法里没有处理中断信号的逻辑,那么中断会被忽略。- execution.isolation.thread.interruptOnCancel
是否在服务取消后中断,默认为false- execution.isolation.semaphore.maxConcurrentRequests
设置信号量最大请求数,需要设置execution.isolation.strategy属性为semaphore,默认为10
Fallback 回退
- fallback.isolation.semaphore.maxConcurrentRequests
fallback回退方法时的最大并发量,超出此并发数的请求会抛出REJECTED_SEMAPHORE_FALLBACK 异常,默认是10- fallback.enabled
是否启用fallback方法回退,默认为true
Circuit Breaker 熔断器
熔断器相关配置
- circuitBreaker.enabled
是否开启熔断器,默认为true- circuitBreaker.requestVolumeThreshold
在一定的窗口时间内,打开熔断器的最小请求数,例如设置为20,在10s的窗口中,有19个线程,即使19个线程都失败了,也不会打开熔断器。默认为20- circuitBreaker.sleepWindowInMilliseconds
熔断器打开后,所有请求都会快速失败,然后过一段时间,Hystrix会放行一个请求,进行测试是否恢复,如果恢复则关闭熔断器。这个属性就是用于指定熔断器开启后过多久会进行一次尝试,默认是5000毫秒- circuitBreaker.errorThresholdPercentage
设置窗口时间内多少百分比的请求失败会打开熔断器,默认是50,也就是超过50%的失败了才会打开
circuitBreaker.forceOpen、circuitBreaker.forceClosed
是否强制开启或者强制关闭,默认是false