微服務系列之Hystrix服務容錯(三)

本篇文章為系列文章,未讀前兩集的同學請猛戳這裡:


服務熔斷

  


  服務熔斷一般是指軟件系統中,由於某些原因使得服務出現了過載現象,為防止造成整個系統故障,從而採用的一種保護措施,所以很多地方把熔斷亦稱為過載保護。

  

微服務系列之Hystrix服務容錯(三)

  

添加依賴

  

  服務消費者 pom.xml 添加 hystrix 依賴。

<code> 
 <dependency>
     <groupid>org.springframework.cloud/<groupid>
     <artifactid>spring-cloud-starter-netflix-hystrix/<artifactid>
 /<dependency>/<code>

  

業務層

  

  服務消費者業務層代碼添加服務熔斷規則。

<code> package com.example.service.impl;
 ​
 import com.example.pojo.Order;
 import com.example.service.OrderService;
 import com.example.service.ProductService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 ​
 import java.util.Arrays;
 ​
 @Service
 public class OrderServiceImpl implements OrderService {
 ​
     @Autowired
     private ProductService productService;
 ​
     /**
      * 根據主鍵查詢訂單
      *
      * @param id
      * @return

      */
     @Override
     public Order searchOrderById(Integer id) {
         return new Order(id, "order-003", "中國", 2666D,
                 // 為了方便測試直接使用訂單 ID 作為參數
                 Arrays.asList(productService.selectProductById(id)));
    }
 ​
 }/<code>

  

  OrderServiceImpl.java

<code> package com.example.service.impl;
 ​
 import com.example.pojo.Order;
 import com.example.service.OrderService;
 import com.example.service.ProductService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 ​
 import java.util.Arrays;
 ​
 @Service
 public class OrderServiceImpl implements OrderService {
 ​
     @Autowired
     private ProductService productService;
 ​
     /**
      * 根據主鍵查詢訂單
      *
      * @param id
      * @return
      */
     @Override
     public Order searchOrderById(Integer id) {
         return new Order(id, "order-003", "中國", 2666D,
                 // 為了方便測試直接使用訂單 ID 作為參數
                 Arrays.asList(productService.selectProductById(id)));
    }
 ​
 }/<code>

  

啟動類

  

  服務消費者啟動類開啟熔斷器註解。

<code> package com.example;
 ​
 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.context.annotation.Bean;
 import org.springframework.web.client.RestTemplate;
 ​
 // 開啟熔斷器註解 2 選 1,@EnableHystrix 封裝了 @EnableCircuitBreaker
 // @EnableHystrix
 @EnableCircuitBreaker
 @SpringBootApplication
 public class OrderServiceRestApplication {
 ​
     @Bean
     @LoadBalanced
     public RestTemplate restTemplate() {
         return new RestTemplate();
    }
 ​
     public static void main(String[] args) {
         SpringApplication.run(OrderServiceRestApplication.class, args);
    }
 ​
 }/<code>

  

測試

  

  訪問:http://localhost:9090/order/1/product 結果如下:

<code> -----selectProductById-----22:47:12.463
 -----selectProductById-----22:47:17.677
 -----selectProductById-----22:47:22.894/<code>

  通過結果可以看到,服務熔斷已經啟用。每 5 秒會去重試一次 Provider 如果重試失敗繼續返回託底數據,如此反覆直到服務可用,然後關閉熔斷快速恢復。

  

服務降級

  

微服務系列之Hystrix服務容錯(三)

  

  吃雞遊戲相信大家應該都有所耳聞,這個遊戲落地的時候什麼東西都沒有,裝備都是需要自己去主動搜索或者通過擊殺其他隊伍而獲取。所以,在這個遊戲中就涉及到一個揹包的問題,揹包的大小決定了能攜帶的物資數量,總共分為三級,在你沒有拿到更高級的揹包之前,你只能將最重要的裝備留在身邊。其實服務降級,就是這麼回事,再看一個例子。

  大家都見過女生旅行吧,大號的旅行箱是必備物,平常走走近處綽綽有餘,但一旦出個遠門,再大的箱子都白搭了,怎麼辦呢?常見的情景就是把物品拿出來分分堆,比了又比,最後一些非必需品的就忍痛放下了,等到下次箱子夠用了,再帶上用一用。而服務降級,就是這麼回事,整體資源快不夠了,忍痛將某些服務先關掉,待渡過難關,再開啟回來。

  

觸發條件

  

  • 方法拋出非 HystrixBadRequestException 異常;
  • 方法調用超時;
  • 熔斷器開啟攔截調用;
  • 線程池/隊列/信號量跑滿。

  

添加依賴

  

  服務消費者 pom.xml 添加 hystrix 依賴。

<code> 
 <dependency>
     <groupid>org.springframework.cloud/<groupid>
     <artifactid>spring-cloud-starter-netflix-hystrix/<artifactid>
 /<dependency>/<code>

  

業務層

  

  服務消費者業務層代碼添加服務降級規則。

<code> import com.example.pojo.Product;
 import com.example.service.ProductService;
 import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.RestTemplate;
 ​
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 ​
 /**
  * 商品管理
  */
 @Service
 public class ProductServiceImpl implements ProductService {

 ​
     @Autowired
     private RestTemplate restTemplate;
 ​
     /**
      * 根據主鍵查詢商品
      *
      * @param id
      * @return
      */
     // 聲明需要服務容錯的方法
     // 服務降級
     @HystrixCommand(fallbackMethod = "selectProductByIdFallback")
     @Override
     public Product selectProductById(Integer id) {
         return restTemplate.getForObject("http://product-service/product/" + id, Product.class);
    }
 ​
     // 託底數據
     private Product selectProductByIdFallback(Integer id) {
         return new Product(id, "託底數據", 1, 2666D);
    }
 ​
 }/<code>

  

啟動類

  

  服務消費者啟動類開啟熔斷器註解。

<code> package com.example;
 ​
 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.context.annotation.Bean;
 import org.springframework.web.client.RestTemplate;
 ​
 // 開啟熔斷器註解 2 選 1,@EnableHystrix 封裝了 @EnableCircuitBreaker
 // @EnableHystrix

 @EnableCircuitBreaker
 @SpringBootApplication
 public class OrderServiceRestApplication {
 ​
     @Bean
     @LoadBalanced
     public RestTemplate restTemplate() {
         return new RestTemplate();
    }
 ​
     public static void main(String[] args) {
         SpringApplication.run(OrderServiceRestApplication.class, args);
    }
 ​
 }/<code>

  

測試

  

  訪問:http://localhost:9090/order/3/product 結果如下:

微服務系列之Hystrix服務容錯(三)

  關閉服務提供者,再次訪問:http://localhost:9090/order/3/product 結果如下:

微服務系列之Hystrix服務容錯(三)

  通過結果可以看到,服務降級已經啟用。當 Provider 不可用時返回託底數據,直到服務可用快速恢復。

  

Feign 雪崩處理

  

環境準備

  

  我們在父工程下再創建一個 Consumer 項目這次是基於 Feign 實現聲明式服務調用。

微服務系列之Hystrix服務容錯(三)

微服務系列之Hystrix服務容錯(三)

微服務系列之Hystrix服務容錯(三)

微服務系列之Hystrix服務容錯(三)

微服務系列之Hystrix服務容錯(三)

  

添加依賴

  

  服務提供者添加 openfeign 依賴,openfeign 默認集成了 hystrix 依賴。

<code>

<project> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelversion>4.0.0/<modelversion>

<groupid>com.example/<groupid>
<artifactid>order-service-feign/<artifactid>

<version>1.0-SNAPSHOT/<version>


<parent>
<groupid>com.example/<groupid>
<artifactid>hystrix-demo/<artifactid>
<version>1.0-SNAPSHOT/<version>
/<parent>


<dependencies>

<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-netflix-eureka-client/<artifactid>
/<dependency>

<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-openfeign/<artifactid>
/<dependency>

<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-web/<artifactid>
/<dependency>

<dependency>
<groupid>org.projectlombok/<groupid>
<artifactid>lombok/<artifactid>
<scope>provided/<scope>
/<dependency>


<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-test/<artifactid>
<scope>test/<scope>
<exclusions>
<exclusion>
<groupid>org.junit.vintage/<groupid>
<artifactid>junit-vintage-engine/<artifactid>

/<exclusion>
/<exclusions>
/<dependency>
/<dependencies>

/<project>/<code>

  

配置文件

  

  服務提供者需要開啟 Feign 對於 Hystrix 的支持。

<code>server:
port: 9091 # 端口

spring:
application:
name: order-service-feign # 應用名稱

# 配置 Eureka Server 註冊中心
eureka:
instance:
prefer-ip-address: true # 是否使用 ip 地址註冊
instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
client:
service-url: # 設置服務註冊中心地址
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

# Feign 開啟 Hystrix 支持
feign:
hystrix:
enabled: true/<code>

  

實體類

  

  Product.java

<code>package com.example.pojo; 


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {

private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;

}/<code>

  

  Order.java

<code>package com.example.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {

private Integer id;
private String orderNo;
private String orderAddress;
private Double totalPrice;
private List<product> productList;

}/<product>/<code>

  

消費服務

  

  ProductService.java

<code>package com.example.service;

import com.example.fallback.ProductServiceFallback;
import com.example.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

// 聲明需要調用的服務和服務熔斷處理類
@FeignClient(value = "product-service", fallback = ProductServiceFallback.class)
public interface ProductService {

/**
* 查詢商品列表
*
* @return
*/
@GetMapping("/product/list")
List<product> selectProductList();

/**
* 根據多個主鍵查詢商品
*
* @param ids
* @return
*/
@GetMapping("/product/listByIds")
List<product> selectProductListByIds(@RequestParam("id") List<integer> ids);

/**
* 根據主鍵查詢商品
*
* @param id
* @return
*/
@GetMapping("/product/{id}")
Product selectProductById(@PathVariable("id") Integer id);

}/<integer>/<product>/<product>/<code>

  

  OrderService.java

<code>package com.example.service;

import com.example.pojo.Order;

public interface OrderService {

/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
Order selectOrderById(Integer id);

/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
Order queryOrderById(Integer id);

/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
Order searchOrderById(Integer id);

}/<code>

  

  OrderServiceImpl.java

<code>package com.example.service.impl;

import com.example.pojo.Order;
import com.example.service.OrderService;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;

@Service
public class OrderServiceImpl implements OrderService {

@Autowired
private ProductService productService;

/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
@Override
public Order selectOrderById(Integer id) {
return new Order(id, "order-001", "中國", 22788D,
productService.selectProductList());
}

/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
@Override
public Order queryOrderById(Integer id) {
return new Order(id, "order-002", "中國", 11600D,
productService.selectProductListByIds(Arrays.asList(1, 2)));
}

/**
* 根據主鍵查詢訂單
*
* @param id
* @return
*/
@Override
public Order searchOrderById(Integer id) {
return new Order(id, "order-003", "中國", 2666D,
Arrays.asList(productService.selectProductById(5)));
}

}/<code>

  

熔斷降級

  

  ProductServiceFallback.java

<code>package com.example.fallback;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* 服務熔斷降級處理
*/
@Component
public class ProductServiceFallback implements ProductService {

// 查詢商品列表接口的託底數據
@Override
public List<product> selectProductList() {
return Arrays.asList(
new Product(1, "託底數據-華為手機", 1, 5800D),
new Product(2, "託底數據-聯想筆記本", 1, 6888D),
new Product(3, "託底數據-小米平板", 5, 2020D)
);
}

// 根據多個主鍵查詢商品接口的託底數據
@Override
public List<product> selectProductListByIds(List<integer> ids) {
List<product> products = new ArrayList<>();
ids.forEach(id -> products.add(new Product(id, "託底數據-電視機" + id, 1, 5800D)));
return products;
}

// 根據主鍵查詢商品接口的託底數據

@Override
public Product selectProductById(Integer id) {
return new Product(id, "託底數據", 1, 2666D);
}

}/<product>/<integer>/<product>/<product>/<code>

  

控制層

  

  OrderController.java

<code>package com.example.controller;

import com.example.pojo.Order;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {

@Autowired
private OrderService orderService;

/**
* 根據主鍵查詢訂單-調用商品服務 /product/list
*
* @param id
* @return
*/
@GetMapping("/{id}/product/list")
public Order selectOrderById(@PathVariable("id") Integer id) {
return orderService.selectOrderById(id);
}

/**
* 根據主鍵查詢訂單-調用商品服務 /product/listByIds
*
* @param id

* @return
*/
@GetMapping("/{id}/product/listByIds")
public Order queryOrderById(@PathVariable("id") Integer id) {
return orderService.queryOrderById(id);
}

/**
* 根據主鍵查詢訂單-調用商品服務 /product/{id}
*
* @param id
* @return
*/
@GetMapping("/{id}/product")
public Order searchOrderById(@PathVariable("id") Integer id) {
return orderService.searchOrderById(id);
}

}/<code>

  

啟動類

  

  服務消費者啟動類開啟 @EnableFeignClients 註解。

<code>package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
// 開啟 FeignClients 註解
@EnableFeignClients
public class OrderServiceFeignApplication {

public static void main(String[] args) {
SpringApplication.run(OrderServiceFeignApplication.class, args);
}

}/<code>

  

捕獲服務異常

  

  我們已經可以通過 Feign 實現服務降級處理,但是服務不可用時如果我們想要捕獲異常信息該如何實現?接下來一起學習一下。

  

消費服務

  

  通過 fallbackFactory 屬性聲明服務熔斷降級處理類。

<code>package com.example.service;

import com.example.fallback.ProductServiceFallbackFactory;
import com.example.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

// 聲明需要調用的服務和服務熔斷處理類
@FeignClient(value = "product-service", fallbackFactory = ProductServiceFallbackFactory.class)
public interface ProductService {

/**
* 查詢商品列表
*
* @return
*/
@GetMapping("/product/list")
List<product> selectProductList();

/**
* 根據多個主鍵查詢商品

*
* @param ids
* @return
*/
@GetMapping("/product/listByIds")
List<product> selectProductListByIds(@RequestParam("id") List<integer> ids);

/**
* 根據主鍵查詢商品
*
* @param id
* @return
*/
@GetMapping("/product/{id}")
Product selectProductById(@PathVariable("id") Integer id);

}/<integer>/<product>/<product>/<code>

  

熔斷降級

  

  實現 FallbackFactory 接口。

<code>package com.example.fallback;

import com.example.pojo.Product;
import com.example.service.ProductService;
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* 服務熔斷降級處理可以捕獲異常
*/
@Component
public class ProductServiceFallbackFactory implements FallbackFactory<productservice> {

// 獲取日誌,在需要捕獲異常的方法中進行處理
Logger logger = LoggerFactory.getLogger(ProductServiceFallbackFactory.class);

@Override
public ProductService create(Throwable throwable) {
return new ProductService() {
// 查詢商品列表接口的託底數據
@Override
public List<product> selectProductList() {
logger.error("product-service 服務的 selectProductList 方法出現異常,異常信息如下:"
+ throwable);
return Arrays.asList(
new Product(1, "託底數據-華為手機", 1, 5800D),
new Product(2, "託底數據-聯想筆記本", 1, 6888D),
new Product(3, "託底數據-小米平板", 5, 2020D)
);
}

// 根據多個主鍵查詢商品接口的託底數據
@Override
public List<product> selectProductListByIds(List<integer> ids) {
logger.error("product-service 服務的 selectProductListByIds 方法出現異常,異常信息如下:"
+ throwable);
List<product> products = new ArrayList<>();
ids.forEach(id -> products.add(new Product(id, "託底數據-電視機" + id, 1, 5800D)));
return products;
}

// 根據主鍵查詢商品接口的託底數據
@Override
public Product selectProductById(Integer id) {
logger.error("product-service 服務的 selectProductById 方法出現異常,異常信息如下:"
+ throwable);
return new Product(id, "託底數據", 1, 2666D);
}

};
}

}/<product>/<integer>/<product>/<product>/<productservice>/<code>

  

測試

  

  訪問:http://localhost:9091/order/1/product/list 結果如下:

微服務系列之Hystrix服務容錯(三)

  

  控制檯打印結果:

<code>ERROR 17468 --- [ HystrixTimer-1] c.e.f.ProductServiceFallbackFactory      : product-service 服務的 selectProductListByIds 方法出現異常,異常信息如下:com.netflix.hystrix.exception.HystrixTimeoutException/<code> 

  至此 Hystrix 服務容錯知識點就講解結束了。

微服務系列之Hystrix服務容錯(三)

您的 點贊 和 轉發 是對我最大的支持。

薇信掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~

微服務系列之Hystrix服務容錯(三)


分享到:


相關文章: