今天先來說說“服務熔斷”和“服務降級”。
服務熔斷:在股票市場,熔斷這個詞大家都不陌生,是指當股指波幅達到某個點后,交易所為控制風險采取的暫停交易措施。相應的,服務熔斷一般是指軟件系統中,由於某些原因使得服務出現了過載現象,為防止造成整個系統故障,從而采用的一種保護措施,所以很多地方把熔斷亦稱為過載保護。
服務降級:大家都見過女生旅行吧,大號的旅行箱是必備物,平常走走近處綽綽有余,但一旦出個遠門,再大的箱子都白搭了,怎么辦呢?常見的情景就是把物品拿出來分分堆,比了又比,最后一些非必需品的就忍痛放下了,等到下次箱子夠用了,再帶上用一用。而服務降級,就是這么回事,整體資源快不夠了,忍痛將某些服務先關掉,待渡過難關,再開啟回來。
所以從上述分析來看,兩者其實從有些角度看是有一定的類似性的:
1)目的很一致,都是從可用性可靠性着想,為防止系統的整體緩慢甚至崩潰,采用的技術手段;
2)最終表現類似,對於兩者來說,最終讓用戶體驗到的是某些功能暫時不可達或不可用;
3)粒度一般都是服務級別,當然,業界也有不少更細粒度的做法,比如做到數據持久層(允許查詢,不允許增刪改);
4)自治性要求很高,熔斷模式一般都是服務基於策略的自動觸發,降級雖說可人工干預,但在微服務架構下,完全靠人顯然不可能,開關預置、配置中心都是必要手段;
而兩者的區別也是明顯的:
1)觸發原因不太一樣,服務熔斷一般是某個服務(下游服務)故障引起,而服務降級一般是從整體負荷考慮;
2)管理目標的層次不太一樣,熔斷其實是一個框架級的處理,每個微服務都需要(無層級之分),而降級一般需要對業務有層級之分(比如降級一般是從最外圍服務開始)
3)實現方式不太一樣
參考文章:https://blog.csdn.net/guwei9111986/article/details/51649240
下面介紹 Hystrix:
在分布式環境中,許多服務依賴項中的一些必然會失敗。Hystrix 是一個庫,通過添加延遲容忍和容錯邏輯,幫助你控制這些分布式服務之間的交互。Hystrix 通過隔離服務之間的訪問點、停止級聯失敗和提供回退選項來實現這一點,所有這些都可以提高系統的整體彈性。
Hystrix 提供了熔斷、隔離、Fallback、Cache、監控等功能。出現錯誤之后可以 fallback 錯誤的處理信息,返回一些兜底數據等等。
https://www.cnblogs.com/cjsblog/p/9391819.html
本文的示例承接上一篇文章:https://www.cnblogs.com/jwen1994/p/11408511.html
1. Feign 結合 Hystrix 斷路器開發
第一步:加入依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
注意新舊版本問題,所以要以官網為主,不然部分注解會丟失
第二步:啟動類里面增加注解 @EnableCircuitBreaker
@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(); } }
我們也可以使用 SpringCloudApplication 注解,它包含了很多 Spring Cloud 相關的注解
第三步:最外層 api 使用
api 方法上增加 @HystrixCommand(fallbackMethod = "saveOrderFail")。好比異常處理(網絡異常,參數或者內部調用問題)
@RestController @RequestMapping("api/v1/order") public class OrderController { @Autowired private 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; } //注意,方法簽名一定要要和api方法一致 private Object saveOrderFail(int userId, int productId){ Map<String, Object> msg = new HashMap<>(); msg.put("code", -1); msg.put("msg", "搶購人數太多,您被擠出來了,稍等重試"); return msg; } }
注意:編寫 fallback 方法實現,方法簽名一定要和 api 方法簽名一致
第四步:進行測試,我們使用一個會出錯的請求,它會返回 saveOrderFail 的返回值
2. 我們調用服務時,如果服務出錯,我們希望可以進行一些處理
第一步:開啟 Feign 支持 Hystrix (注意,一定要開啟,舊版本默認支持,新版本默認關閉)
feign:
hystrix:
enabled: true
第二步:FeignClient(name="xxx", fallback=xxx.class ),class 需要繼承當前 FeignClient 的類
@FeignClient(name = "product-service", fallback = ProductClientFallback.class) public interface ProductClient { @GetMapping("/api/v1/product/find") String findById(@RequestParam(value = "id") int id); }
ProductClientFallback 類
@Component public class ProductClientFallback implements ProductClient { @Override public String findById(int id) { System.out.println("feign 調用product-service findbyid 異常"); return null; } }
3. 進一步完善異常報警通知
我們可以試着加入 Redis 來實現一個異常報警
第一步:加入 Redis 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
第二步:配置 Redis 鏈接信息
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
timeout: 2000
第三步:修改代碼
@RestController @RequestMapping("api/v1/order") public class OrderController { @Autowired private ProductOrderService productOrderService; @Autowired private StringRedisTemplate 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; } //注意,方法簽名一定要要和api方法一致 private Object saveOrderFail(int userId, int productId, HttpServletRequest request){ //監控報警 String saveOrderKye = "save-order"; String sendValue = redisTemplate.opsForValue().get(saveOrderKye); final String ip = request.getRemoteAddr(); new Thread( ()->{ if (StringUtils.isBlank(sendValue)) { System.out.println("緊急短信,用戶下單失敗,請離開查找原因,ip地址是="+ip); //發送一個http請求,調用短信服務 TODO redisTemplate.opsForValue().set(saveOrderKye, "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; } }
4. Hystrix 降級策略和調整
1)查看默認講解策略 HystrixCommandProperties
這個文件里可以看到所有默認的策略
2)execution.isolation.strategy 隔離策略
Hystrix 有兩種隔離策略:THREAD 線程池隔離 (默認)、SEMAPHORE 信號量。信號量適用於接口並發量高的情況,如每秒數千次調用的情況,導致的線程開銷過高,通常只適用於非網絡調用,執行速度快
3)execution.isolation.thread.timeoutInMilliseconds 超時時間
Hystrix 默認超時時間為1000毫秒
4)execution.timeout.enabled 是否開啟超時限制 (一定不要禁用)
Hystrix 默認是開啟超時限制的
5)execution.isolation.semaphore.maxConcurrentRequests 隔離策略為 信號量的時候,如果達到最大並發數時,后續請求會被拒絕,默認是10
#把hystrix超時時間禁用
#hystrix:
# command:
# default:
# execution:
# timeout:
# enabled: false
#execution.isolation.thread.timeoutInMilliseconds=4000
#設置超時時間
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
官方文檔:https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.strategy
5. 斷路器 Dashboard 監控儀表盤
生產環境幾乎不用,只要做好異常告警就可以
第一步:加入依賴
<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
第三步:配置文件增加 endpoint
#暴露全部的監控信息
management:
endpoints:
web:
exposure:
include: "*"
第四步:訪問入口
1)訪問:http://localhost:8781/hystrix
2)Hystrix Dashboard 輸入: http://localhost:8781/actuator/hystrix.stream
補充: 如果從 Maven 中心倉庫下載太慢,可是修改 Maven 倉庫地址,使用其他 Maven 倉庫。為了使用阿里雲的倉庫,我們在 pom.xml 中修改
<repositories> <repository> <id>nexus-aliyun</id> <name>Nexus aliyun</name> <layout>default</layout> <url>http://maven.aliyun.com/nexus/content/groups/public</url> <snapshots> <enabled>false</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> </repositories>