斷路器是電路中的一個保護電路安全的開關,當電路出現短路時,斷路器會自動跳閘,防止出現電路故障。
一個微服務架構的系統中也需要這種保護裝置,當消費者調用某一個服務的時候,如當前的服務有異常,譬如服務已經掛了,這時候就需要斷路器來把當前調用的服務斷開,Spring Cloud中集成的斷路器組件為:Hystrix。如圖所示,Hystrix在調用服務失敗的情況下會進行回退或者降級處理,比如快速失敗、無聲失敗、返回默認值、自己組裝一個返回值、利用遠程緩存、主次方式回退等回退類型。
降級回退相關資料:https://www.jianshu.com/p/3e11ac385c73?from=timeline

以上一章的調用用戶服務為例,先實現Java端的再移植到.net core
1.服務調用設置斷路器java版
在Spring Cloud官方文檔搜索斷路器:Circuit Breaker
官方文檔示例:

1.1 添加斷路器依賴
斷路器是在消費者調用時添加的,首先在orderservice上添加Hystrix依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
在啟動類上添加@EnableCircuitBreaker注解來啟用Hystrix
1.2 指定調用失敗退回方法
在調用服務類UserService各個方法中添加回退錯誤的方法,並使用@HystrixCommand注解指定回退的方法
@Service public class UserService { @Autowired private RestTemplate restTemplate; private String serviceUrl="http://userservice/user"; @HystrixCommand(fallbackMethod = "getAllError") public List<User> getAll() { ParameterizedTypeReference<List<User>> responseType = new ParameterizedTypeReference<List<User>>(){}; ResponseEntity<List<User>> resp = restTemplate.exchange(serviceUrl+"/getall", HttpMethod.GET, null, responseType); List<User> list = resp.getBody(); return list; } @HystrixCommand(fallbackMethod = "getPortError") public String getPort(){ String msg = restTemplate.getForObject(serviceUrl+"/getport", String.class); return msg; } public User getByName(String name){ User user = restTemplate.getForObject(serviceUrl+"/getbyname?name="+name, User.class); return user; } //getAll回退方法 public List<User> getAllError() { return null; } //getPort回退方法 public String getPortError() { return "userservice服務斷開,getPort方法調用錯誤!"; } }
把userservice服務停掉,調用一下訂單服務的獲取端口方法,進入了錯誤方法了,說明已經成功設置回退方法。

啟動服務后,調用成功。

1.3 設置超時時間
如果調用一直進入回退方法,可能是Hystrix沒設置超時時間,配置下超時時間即可。
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 10000 #設置超時時間 10秒
2.服務調用設置斷路器.net core版
2.1 添加斷路器引用
首先在OrderService項目中添加Steeltoe.CircuitBreaker.HystrixCore引用

2.2 創建Command類來指定退回方法
在調用用戶服務UserService類上繼承HystrixCommand<string>
官方文檔:http://steeltoe.io/docs/steeltoe-circuitbreaker/#1-2-8-use-commands
.net core版比較麻煩的是,不能直接在Service的方法上加特性來聲明要回退的方法,而是每個方法要都用一個繼承自HystrixCommand<>的泛型方法,泛型類型為方法返回的類型,然后再調用這個類的方法,Java版直接用注解還是方便很多。
可以參考SteeltoeOSS的例子:https://github.com/SteeltoeOSS/Samples/tree/dev/CircuitBreaker/src/AspDotNetCore/FortuneTeller/Fortune-Teller-UI/Services

兩個Command類對應Service里面的兩個方法。
Command類我們按照服務名(去掉后面的Service)+方法名+Command來命名,方便確定是調用的那個方法,譬如獲取所有用戶的類:UsergetAllCommand。
using System.Collections.Generic; using System.Threading.Tasks; using Steeltoe.CircuitBreaker.Hystrix; namespace OrderService.Controllers { public class UsergetAllCommand : HystrixCommand<List<User>> { private IUserService _userService; public UsergetAllCommand(IHystrixCommandOptions options,IUserService userService) : base(options) { _userService = userService; IsFallbackUserDefined = true; } public async Task<List<User>> getAll() { return await ExecuteAsync(); } protected override async Task<List<User>> RunAsync() { var result = await _userService.getAll(); return result; } /// <summary> /// 回退方法 /// </summary> /// <returns></returns> protected override async Task<List<User>> RunFallbackAsync() { return null; } } }
同樣再創建一個getPort的命令類,然后在Startup類中的ConfigureServices配置HystrixCommand類的注入
// 注冊使用HystrixCommand類封裝UserService方法做斷路器的命令類 services.AddHystrixCommand<UsergetAllCommand>("userservice", Configuration); services.AddHystrixCommand<UsergetPortCommand>("userservice", Configuration);
在OrderController中改為使用Command類來調用userservice的方法。
[Route("[controller]")] [ApiController] public class OrderController : ControllerBase { private readonly IUserService _userService; private readonly UsergetAllCommand _usergetAllCommand; private readonly UsergetPortCommand _usergetPortCommand; //構造方法來注入實例 public OrderController(IUserService userService ,UsergetAllCommand usergetAllCommand ,UsergetPortCommand usergetPortCommand) { _userService = userService; _usergetAllCommand = usergetAllCommand; _usergetPortCommand = usergetPortCommand; } [Route("getalluser")] [HttpGet] public async Task<List<User>> getAll() { //List<User> list = await _userService.getAll(); var list =await _usergetAllCommand.getAll(); return list; } [Route("getuserserviceport")] [HttpGet] public async Task<string> getUserServicePort() { //var port = await _userService.getPort(); var port = await _usergetPortCommand.getPort(); return port; } }
停止userservice服務,成功調用回退方法。

啟動userservice服務后再刷新,成功獲取到數據。

2.3 設置超時時間
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "spring": { "application": { "name": "orderservice" } }, "eureka": { "client": { "serviceUrl": "http://localhost:8881/eureka/", "shouldRegisterWithEureka": true, "shouldFetchRegistry": true }, "instance": { "port": 6660 } }, "hystrix": { "command": { "default": { "execution": { "timeout": { "enabled": true }, "isolation": { "thread": { "timeoutInMilliseconds" : 10000 } } } } } } }
至此斷路器已添加完畢。
