Spring Cloud(5):Hystrix的使用
熔断:类似生活中的保险丝,电流过大就会熔断
降级:类似生活中的旅行,行李箱只有那么大,所以要抛弃一些非必需的物品
熔断降级应用:
某宝双十一商品下单,用户量巨大,于是考虑抛弃相关商品推荐等模块,确保该商品信息和下单功能通畅
熔断和降级的区别以及联系:
1.两者都是为了防止系统崩溃,提高可用性
2.最终给用户的体验是某些功能暂时不可用
3.服务熔断一般是由下游服务故障导致的,服务降级一般是从系统整体负荷考虑,由调用方控制
Hystrix的使用:
基于上一篇Feign的使用:https://www.cnblogs.com/xuyiqing/p/10869026.html
订单服务Order-Service中加入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
在启动类中加入注解
package org.dreamtech.orderservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableFeignClients @EnableCircuitBreaker public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
Controller层进行修改
package org.dreamtech.orderservice.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.dreamtech.orderservice.service.ProductOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/order") public class OrderController { private final ProductOrderService productOrderService; @Autowired public OrderController(ProductOrderService productOrderService) { this.productOrderService = productOrderService; } @RequestMapping("/save") @HystrixCommand(fallbackMethod = "saveOrderFail") public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) { Map<String, Object> data = new HashMap<>(); data.put("code", 0); data.put("data", productOrderService.save(userId, productId)); return data; } private Object saveOrderFail(int userId, int productId) { Map<String, Object> msg = new HashMap<>(); msg.put("code", -1); msg.put("msg", "请稍后重试"); return msg; } }
注意:saveOrderFail方法的参数必须和save的参数一致
依次启动Eureka-Server->Product-Service->Order-Service
访问:http://localhost:8781/api/order/save?user_id=1&product_id=4
正常显示:
{"code":0,"data":{"id":0,"productName":"\"iPhone4 data from port=8771\"","tradeNo":"13933d59-34fd-473a-8dbe-f114bc9fd2b9","price":4444,"createTime":"2019-05-17T03:54:12.682+0000","userId":1,"userName":null}}
这时候我关闭Product-Service,模拟熔断
再次访问:http://localhost:8781/api/order/save?user_id=1&product_id=4
显示:
{"msg":"请稍后重试","code":-1}
说明Hystrix配置和使用成功!
Feign结合Hystrix的使用:
配置Feign允许Hystrix
feign:
hystrix:
enabled: true
FeignClient加入fallback处理
package org.dreamtech.orderservice.service; import org.dreamtech.orderservice.fallback.ProductClientFallback; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "product-service",fallback = ProductClientFallback.class) public interface ProductClient { @RequestMapping("/api/product/find") String findById(@RequestParam(value = "id")int id); }
ProductClientFallback
package org.dreamtech.orderservice.fallback; import org.dreamtech.orderservice.service.ProductClient; import org.springframework.stereotype.Component; @Component public class ProductClientFallback implements ProductClient { @Override public String findById(int id) { System.out.println("Feign调用Product-Service异常"); return null; } }
启动项目,访问:http://localhost:8781/api/order/save?user_id=1&product_id=3成功
关闭Product-Service,访问http://localhost:8781/api/order/save?user_id=1&product_id=3
显示
{"msg":"请稍后重试","code":-1}
并且在控制台打印
Feign调用Product-Service异常
服务异常报警通知:
只是熔断和降级是不够了,还需要报警机制
由于访问量较大,要做到只报警一次,那么需要进行标识
通常实现原理基于缓存,比如Redis
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置
spring: application: name: order-service redis: host: 127.0.0.1 database: 0 port: 6379 timeout: 2000
代码
package org.dreamtech.orderservice.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.apache.commons.lang.StringUtils; import org.dreamtech.orderservice.service.ProductOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/api/order") public class OrderController { private final ProductOrderService productOrderService; private final StringRedisTemplate redisTemplate; @Autowired public OrderController(ProductOrderService productOrderService, StringRedisTemplate redisTemplate) { this.productOrderService = productOrderService; this.redisTemplate = redisTemplate; } @RequestMapping("/save") @HystrixCommand(fallbackMethod = "saveOrderFail") public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) { Map<String, Object> data = new HashMap<>(); data.put("code", 0); data.put("data", productOrderService.save(userId, productId)); return data; } private Object saveOrderFail(int userId, int productId) { String saveOrderKey = "save-order"; String sendValue = redisTemplate.opsForValue().get(saveOrderKey); new Thread(() -> { if (StringUtils.isBlank(sendValue)) { // TODO 由于没有短信发送接口,模拟发送短信 System.out.println("短信发送成功"); redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS); } else { System.out.println("已经发送过短信,20秒内不重复发送"); } }).start(); Map<String, Object> msg = new HashMap<>(); msg.put("code", -1); msg.put("msg", "请稍后重试"); return msg; } }
测试
启动项目,访问http://localhost:8781/api/order/save?user_id=1&product_id=3没有问题
关闭Product-Service再访问,控制台打印:
Feign调用Product-Service异常
短信发送成功
再次访问:
Feign调用Product-Service异常
已经发送过短信,20秒内不重复发送
等待20秒访问:
Feign调用Product-Service异常
短信发送成功
成功
或者更进一步,拿到IP,发送短信
package org.dreamtech.orderservice.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.apache.commons.lang.StringUtils; import org.dreamtech.orderservice.service.ProductOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/api/order") public class OrderController { private final ProductOrderService productOrderService; private final StringRedisTemplate redisTemplate; @Autowired public OrderController(ProductOrderService productOrderService, StringRedisTemplate redisTemplate) { this.productOrderService = productOrderService; this.redisTemplate = redisTemplate; } @RequestMapping("/save") @HystrixCommand(fallbackMethod = "saveOrderFail") public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId, HttpServletRequest request) { Map<String, Object> data = new HashMap<>(); data.put("code", 0); data.put("data", productOrderService.save(userId, productId)); return data; } private Object saveOrderFail(int userId, int productId,HttpServletRequest request) { String saveOrderKey = "save-order"; String sendValue = redisTemplate.opsForValue().get(saveOrderKey); String ip = request.getRemoteAddr(); new Thread(() -> { if (StringUtils.isBlank(sendValue)) { // TODO 由于没有短信发送接口,模拟发送短信 System.out.println("短信发送成功,紧急情况,用户下单失败!来自于IP:"+ip); redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS); } else { System.out.println("已经发送过短信,20秒内不重复发送"); } }).start(); Map<String, Object> msg = new HashMap<>(); msg.put("code", -1); msg.put("msg", "请稍后重试"); return msg; } }
Hystrix自定义配置:
关闭超时时间(实际情况不要用)
hystrix:
command:
default:
execution:
timeout:
enabled: false
修改超时时间为4秒:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
不过Feign默认超时时间少于4秒,所以对Feign也进行修改:
feign: hystrix: enabled: true client: config: default: connectTimeout: 4000 readTimeout: 4000
感觉YML的配置真的不舒服,不如继续用PROPERTIES
Hystrix-Dashboard:对Hystrix过程和结果的可视化界面
Hystrix-Dashboard的使用:
加入依赖:不只是Dashboard,还依赖于SpringBoot的监控Actuator
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
在启动类中加入注解:@EnableHystrixDashboard
package org.dreamtech.orderservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableFeignClients @EnableCircuitBreaker @EnableHystrixDashboard public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
启动后访问:http://localhost:8781/hystrix
根据提示输入http://localhost:8781/actuator/hystrix.stream,发现报错:
Unable to connect to Command Metric Stream.
而且我如我直接访问http://localhost:8781/actuator/hystrix.stream也会显示404
解决:配置文件
management:
endpoints:
web:
exposure:
include: "*"
原因:配置Actuator开启全部端点
启动后访问:http://localhost:8781/hystrix
根据提示输入http://localhost:8781/actuator/hystrix.stream
或者直接访问:http://localhost:8781/actuator/hystrix.stream不会报错