1、前言
作者最近在学习springcloud,本篇文章仅作为学习笔记,如有错误,敬请指正
2、Hystrix是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库
,在分布式系统里,许多依赖不可避免的会调用失败,比如
超时、异常等,Hystrix能够保证在一个依赖出问题的情况
下,不会导致整体服务失败,避免级联故障,以提高分布式
系统的弹性。
Hystrix包括服务降级与熔断
使用情景
我们可以模拟高并发情况来测试服务,了解Hystrix的使用场景
1)新建模块,导入依赖
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
2)修改appliction.yml配置文件,这里设置为单机Eureka,方便模拟测试
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
3)在启动类上添加注释@EnableEurekaClient,注册服务
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
4)创建两个service类,里面包含两个方法,其中一个进行线程等待
@Service
public class PaymentService {
public String paymentInfo_ok(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
}
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000")
})
public String paymentInfo_TimeOut(Integer id)
{
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
}
5)创建controller类,调用service方法
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_ok(id);
log.info("****result: "+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("****result: "+result);
return result;
}
6)开启Jmeter,设置两万次请求并启动
7)我们手动访问http://localhost:8001/payment/hystrix/ok/1,此时会发现这个测试路径竟然也会等待
8)当然,我们一般是使用EurekaClient端服务消
费者来访问的,我们可以创建一个服务来模拟
主要所需依赖如下
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
配置文件如下
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
启动类如下
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80
{
public static void main(String[] args)
{
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
创建Service类,如下
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService
{
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
创建Controller类,如下
@RestController
@Slf4j
public class OrderHystirxController
{
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
同理,高并发测试http://localhost/consumer/payment/hystrix/ok/31,会发现一样会有等待。其原因是tomcat的线程已经全部被占
9)结论
正因为有上述故障或不佳表现才有服务降解与熔断等技术的诞生
3 服务降级
服务降级是当服务器压力剧增的情况下,根据当前业务情况
及流量对一些服务和页面有策略的降级,以此释放服务器资
源以保证核心任务的正常运行
降级配置
1)修改上面8001端口的Service类,加上注解@HystrixCommand和@HystrixProperty
@Service
public class PaymentService {
public String paymentInfo_ok(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
}
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1000")
})
public String paymentInfo_TimeOut(Integer id)
{
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "/(ㄒoㄒ)/调用支付接口超时或异常:\t"+ "\t当前线程池名字" + Thread.currentThread().getName();
}
2)在8001端口启动类上添加新注解@EnableCircuitBreaker,激活Hystrix
3)同样进行高并发测试,可以这个时候不会等待,而是会直接执行我们设置的保底方法
4)同理80端口也可以设置,在80端口的yml配置文件上加上,开启feign上的的Hystrix功能
feign:
hystrix:
enabled: true
4)在80端口启动类上添加注解@EnableHystrix,激活Hystrix(@EnableHystrix注解继承了@EnableCircuitBreaker,并对它进行了再封装)
5)修改80端口的Controller类
@RestController
@Slf4j
public class PaymentHystirxController
{
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
6)结果
7)问题:这样写fallback方法的话,那么每一个业务方法都需要一个,这会很麻烦,导致代码膨胀
8)解决方法:
9)当然,把方法写在业务类里,我觉得不太好,会增加耦合度。解决方法如下,创建一个service实现类
10)在接口的@FeignClient注解中添加fallback属性,表示使用实现类来进行降级方法的实现
11)在测试前记得把Controller中的@HystrixCommand注解删了,才能起效果
3、服务熔断
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个
系统出现雪崩,暂时停止对该服务的调用。
熔断配置
1)在8001端口服务的Service类上添加如下代码
//=========服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_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"),//失败率达到多少跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
if(id < 0)
{
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
{
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
2)Controller类添加:
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
正常情况:
异常情况:
3)测试服务熔断
当我们多次提交错误请求,然后再提交正确请求会发现正确请求也会报错
服务熔断的三个状态
-
熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
-
熔断开启状态(Open)
在固定时间内(Hystrix默认是10秒),接口调用出错比率达到一个阈值(Hystrix默认为50%)
会进入熔断开 启状态。进入熔断状态后, 后续对该服务接口的调用不再经过网络,直接执
行本地的fallback方法。
-
半熔断状态(Half-Open)
在进入熔断开启状态一段时间之后(Hystrix默认是5秒),熔断器会进入半熔断状态。
所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。
如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。
状态图如下