Hystrix原理及使用
背景介绍
1. 服务雪崩
分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务。
一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩
2.引起服务雪崩和服务雪崩的三个阶段
原因大致有四:
硬件故障
程序bug
缓存击穿(用户大量访问缓存中没有键值,导致大量请求数据库,使数据库压力过大)
用户大量请求
服务雪崩的第一阶段:
服务不可用
调用端重试加大流量
服务调用者不可用
3.解决方案
应用扩容(加机器;升级硬件)
流量控制(限流;关闭重试)
缓存(将用户可能大量访问的数据放入缓存,减少数据库压力)
服务降级
- 服务接口拒绝服务
- 页面拒绝服务
- 延迟持久化
- 随机拒绝服务
服务熔断
入门
Hystrix是一个用于处理分布式系统延期和容错的开源库,Hystrix能保证在一个依赖出问题的情况下,不会导致整体服务失败,提高分布式弹性。
服务降级: 假设对方系统不可用了,向调用方返回一个符合预期的,可处理的备选响应,而不是长时间等待或者抛出无处理的异常。这样就保证了服务方的线程不会被长时间不必要的占用,从而避免故障在分布式系统中蔓延,乃至雪崩。
服务熔断:达到最大访问后直接拒绝访问
服务限流:秒杀高并发等操作,排队有序进行
代码
1.没有引入hystrix的准备工作
建这三个工程模拟Hystrix服务降级
EurekaMain7001代码:
pom:
<!--Eureka server--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
yml:
server: port: 7001 eureka: instance: hostname: eureka7001.com client: #表示不往注册中心注册自己 register-with-eureka: false #表示自己端就是注册中心,我的职责就是维护实例 fetch-registry: false #设置地址 service-url: defaultZone: http://eureka7001.com:7001/eureka/
主启动类:
@SpringBootApplication @EnableEurekaServer public class EurekaMain7001 { public static void main(String[] args) { SpringApplication.run(EurekaMain7001.class, args); } }
HystrixPaymentMain8001代码:
pom:
<!--Eureka server--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
yml:
server: port: 8001 spring: application: name: cloud-payment-hystrix-service eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka/ instance: instance-id: hystrix-payment8001 prefer-ip-address: true
service层:
@Service public class HystrixPaymentService { public String getPaymentInfoSuccess() { return "thread poor: " + Thread.currentThread().getName() + " success"; } public String getPaymentInfoTimeout() { try { int spendTime = 3000; TimeUnit.MILLISECONDS.sleep(spendTime); } catch (InterruptedException e) { e.printStackTrace(); } return "thread poor: " + Thread.currentThread().getName() + " timeout"; } }
controller:
@RestController @Slf4j public class HystrixPaymentController { @Resource HystrixPaymentService service; @GetMapping("/payment/hystrix/ok") public String getPaymentInfoSuccess() { return service.getPaymentInfoSuccess(); } @GetMapping("/payment/hystrix/timeout") public String getPaymentInfoTimeout() { return service.getPaymentInfoTimeout(); } }
主启动类:
@SpringBootApplication @EnableEurekaClient public class HystrixPaymentMain8001 { public static void main(String[] args) { SpringApplication.run(HystrixPaymentMain8001.class, args); } }
HystrixOrderMain80代码:
pom:
<!-- open feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
yml:
server: port: 80 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://localhost:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ ribbon: ReadTimeout: 5000 ConnectTimeout: 5000
service接口:
@Component @FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE") public interface HystrixService { @GetMapping("/payment/hystrix/ok") public String getPaymentInfoSuccess(); @GetMapping("/payment/hystrix/timeout") public String getPaymentInfoTimeout(); }
controller:
@RestController public class HystrixController { @Resource private HystrixService hystrixService; @GetMapping("/consumer/payment/hystrix/ok") public String getPaymentInfoSuccess() { return hystrixService.getPaymentInfoSuccess(); } @GetMapping("/consumer/payment/hystrix/timeout") public String getPaymentInfoFail() { return hystrixService.getPaymentInfoTimeout(); } }
主启动类:
@SpringBootApplication @EnableFeignClients public class HystrixOrderMain80 { public static void main(String[] args) { SpringApplication.run(HystrixOrderMain80.class, args); } }
到此,payment注册到eureka, order用openFeign调用payment过程已经完成。
2.压力测试
用jmeter两万个线程调用timeout接口,再通过chrome访问ok接口,会发现chrome访问的ok接口也会响应过慢。
ok接口也会出现调用过慢,转圈圈
解决1:在服务端(payment8001)熔断降级:
service上:
@Service public class HystrixPaymentService { public String getPaymentInfoSuccess() { return "thread poor: " + Thread.currentThread().getName() + " success"; } @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="2000") }) public String getPaymentInfoTimeout() { try { int spendTime = 5000; TimeUnit.MILLISECONDS.sleep(spendTime); } catch (InterruptedException e) { e.printStackTrace(); } return "thread poor: " + Thread.currentThread().getName() + " timeout"; } public String timeoutHandler() { return "call back fail"; } }
主程序类上:
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class HystrixPaymentMain8001 { public static void main(String[] args) { SpringApplication.run(HystrixPaymentMain8001.class, args); } }
引入pom以解决找不到@HystrixCommand注解问题
<!--解决@HystrixCommand注解找不到问题--> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>RELEASE</version> </dependency>
对payment8001进行测试,超时异常时,熔断降级测试成功:
再试一下运行异常:
@HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="2000") }) public String getPaymentInfoTimeout() { int i = 10/0; return "thread poor: " + Thread.currentThread().getName() + " timeout"; } public String timeoutHandler() { return "call back fail"; }
熔断降级测试也能成功:
解决2:在客户端(order80)熔断降级:
准备工作:先修改HystrixOrderMain80里的超时时间,使直接调用不会超时:
@Service public class HystrixPaymentService { public String getPaymentInfoSuccess() { return "thread poor: " + Thread.currentThread().getName() + " success"; } @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="5000") }) public String getPaymentInfoTimeout() { try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "thread poor: " + Thread.currentThread().getName() + " timeout"; } public String timeoutHandler() { return "call back fail"; } }
在cloud-consumer-hystrix-order80里添加代码:
pom:
<!--解决@HystrixCommand注解找不到问题--> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>RELEASE</version> </dependency>
yml:
feign: hystrix: enabled: true
主程序:
@SpringBootApplication @EnableFeignClients @EnableHystrix public class HystrixOrderMain80 { public static void main(String[] args) { SpringApplication.run(HystrixOrderMain80.class, args); } }
controller:
@RestController public class HystrixController { @Resource private HystrixService hystrixService; @GetMapping("/consumer/payment/hystrix/ok") public String getPaymentInfoSuccess() { return hystrixService.getPaymentInfoSuccess(); } @GetMapping("/consumer/payment/hystrix/timeout") @HystrixCommand(fallbackMethod = "orderTimeoutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="1000") }) public String getPaymentInfoFail() { return hystrixService.getPaymentInfoTimeout(); } public String orderTimeoutHandler() { return "order timeout handler..."; } }
测试:
1.调用服务端接口,不会超时:
2.调用消费端接口,出现了熔断降级,验证成功:
全局服务降级处理:
@RestController @DefaultProperties(defaultFallback = "globalFallBackMethod") public class HystrixController { @Resource private HystrixService hystrixService; @GetMapping("/consumer/payment/hystrix/ok") @HystrixCommand public String getPaymentInfoSuccess() { int i = 10/0; return hystrixService.getPaymentInfoSuccess(); } @GetMapping("/consumer/payment/hystrix/timeout") @HystrixCommand(fallbackMethod = "orderTimeoutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="1000") }) public String getPaymentInfoFail() { return hystrixService.getPaymentInfoTimeout(); } public String orderTimeoutHandler() { return "order timeout handler..."; } public String globalFallBackMethod() { return "global fallback handler..."; } }
测试全局异常熔断ok:
所以,如果是全局的就加@DefaultProperties(defaultFallback = "globalFallBackMethod") 和@HystrixCommand注解,如果有需要单独的fallback的就加
@HystrixCommand(fallbackMethod = "orderTimeoutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="1000") })
在消费端这样添加代码会有冗余重复等现象。所以改进一下,在Feign上添加callback:
@Component @FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-SERVICE", fallback = HystrixServiceImpl.class) public interface HystrixService { @GetMapping("/payment/hystrix/ok") public String getPaymentInfoSuccess(); @GetMapping("/payment/hystrix/timeout") public String getPaymentInfoTimeout(); }
添加一个实现类:
@Service
public class HystrixServiceImpl implements HystrixService {
@Override
public String getPaymentInfoSuccess() {
return "HystrixServiceImpl ok fallback ... ";
}
@Override
public String getPaymentInfoTimeout() {
return "HystrixServiceImpl timeout fallback ... ";
}
}
#controller上面不用加@HystrixCommand,只在service层加代码即可
关闭服务提供端(payment8001)再测试成功: