熔斷:相似生活中的保險絲,電流過大就會熔斷html
降級:相似生活中的旅行,行李箱只有那麼大,因此要拋棄一些非必需的物品java
熔斷降級應用:web
某寶雙十一商品下單,用戶量巨大,因而考慮拋棄相關商品推薦等模塊,確保該商品信息和下單功能通暢redis
熔斷和降級的區別以及聯繫:spring
1.二者都是爲了防止系統崩潰,提升可用性apache
2.最終給用戶的體驗是某些功能暫時不可用api
3.服務熔斷通常是由下游服務故障致使的,服務降級通常是從系統總體負荷考慮,由調用方控制緩存
Hystrix的使用:app
基於上一篇Feign的使用:http://www.javashuo.com/article/p-wdnkmplt-mx.htmlide
訂單服務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不會報錯