因为微服务的盛行,很多公司都将原来细粒度比较大的服务拆分红多个小的服务,让每一个小服务作好本身的事便可。git
通过拆分以后,就避免不了服务之间的相互调用问题!若是调用没有处理好,就有可能形成整个系统的瘫痪,比如说其中一些基础服务出现了故障,那么用到这些基础服务的地方都是要作必定的处理的,不能让它们出现大面积的瘫痪!!!github
正常状况下的解决方案就要对服务进行熔断处理,不能由于提供方出现了问题就让调用方也废了。json
熔断通常是指软件系统中,因为某些缘由使得服务出现了过载现象,为防止形成整个系统故障,从而采用的一种保护措施。api
对于这个问题,Steeltoe的Circuit Breaker是一个不错的选择。本文的示例代码也是基于它的。缓存
Steeltoe是什么呢?Steeltoe能够说是构建微服务的一个解决方案吧。具体的能够访问它的官网:app
http://steeltoe.io/async
回归正题,先来看看官方对Circuit Breaker的描述:ide
What do you do when a service you depend on stops responding? Circuit breakers enable you to bypass a failing service, allowing it time to recover, and preventing your users from seeing nasty error messages. Steeltoe includes a .NET implementation of Netflix Hystrix, a proven circuit breaker implementation with rich metrics and monitoring features.函数
不难发现,Circuit Breaker可让咱们很好的处理失败的服务。它也包含了对Netflix Hystrix的.NET(Core)实现。微服务
关于熔断机制,有个很是经典的图(这里直接拿了官方文档的图),核心描绘的就是三种状态之间的变化关系。
说了那么多,下面仍是来看个简单的例子来略微深刻理解一下吧。
注:服务发现和服务注册不是本文的重点,因此这里不会使用Steeltoe相应的功能。
先定义一个简单的订单服务,这个服务很简单,就一个返回直接返回对应订单号的接口,这里用默认的ASP.NET Core Web API项目作一下调整就行了。
[Route("api/[controller]")] public class ValuesController : Controller { // GET api/values/123 [HttpGet("{id}")] public string Get(string id) { return $"order-{id}"; } }
再来一个新服务去调用上面的订单服务。
先抛开熔断相关的,定义一个用于访问订单服务的Service接口和实现。
public interface IOrderService { Task<string> GetOrderDetailsAsync(string orderId); } public class OrderService : IOrderService { public async Task<string> GetOrderDetailsAsync(string orderId) { using (HttpClient client = new HttpClient()) { return await client.GetStringAsync($"http://localhost:9999/api/values/{orderId}"); } } }
比较简单,就是发起HTTP请求到订单服务,拿一下返回的结果。
忽略熔断的话,如今已经能够经过这个OrderService去拿到结果了。
[HttpGet] public async Task<string> Get([FromServices] Services.IOrderService service, string id = "0") { return await service.GetOrderDetailsAsync(id); }
结果以下:
这是最最最最理想的状况!若是咱们把订单服务停了,会发生什么事呢?
十分尴尬,这个订单服务的调用方也废了。
固然,try-catch也是能够帮咱们处理这个尴尬的问题,但这并非咱们想要的结果啊!
下面来看看引入Circuit Breaker以后如何略微优雅一点去处理这个问题。
定义一个GetOrderDetailsHystrixCommand,让它继承HystrixCommand
public class GetOrderDetailsHystrixCommand : HystrixCommand<string> { private readonly IOrderService _service; private readonly ILogger<GetOrderDetailsHystrixCommand> _logger; private string _orderId; public GetOrderDetailsHystrixCommand( IHystrixCommandOptions options, IOrderService service, ILogger<GetOrderDetailsHystrixCommand> logger ) : base(options) { this._service = service; this._logger = logger; this.IsFallbackUserDefined = true; } public async Task<string> GetOrderDetailsAsync(string orderId) { _orderId = orderId; return await ExecuteAsync(); } protected override async Task<string> RunAsync() { var result = await _service.GetOrderDetailsAsync(_orderId); _logger.LogInformation("Get the result : {0}", result); return result; } protected override async Task<string> RunFallbackAsync() { //断路器已经打开 if (!this._circuitBreaker.AllowRequest) { return await Task.FromResult("Please wait for sometimes"); } _logger.LogInformation($"RunFallback"); return await Task.FromResult<string>($"RunFallbackAsync---OrderId={_orderId}"); } }
这里有几个地方要注意:
接下来要作的是在Startup中进行注册。
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IOrderService, OrderService>(); services.AddHystrixCommand<GetOrderDetailsHystrixCommand>("Order", Configuration); services.AddMvc(); }
能够看到,在添加熔断命令的时候,还用到了Configuration这个参数,这就说明,咱们还少了配置!!
配置是放到appsettings.json里面的,下面来看一下要怎么配置:
{ "hystrix": { "command": { "default": { "circuitBreaker": { //是否启用,默认是true "enabled": true, //在指定时间窗口内,熔断触发的最小个数 "requestVolumeThreshold": 5, //熔断多少时间后去尝试请求 "sleepWindowInMilliseconds": 5000, //失败率达到多少百分比后熔断 "errorThresholdPercentage": 50, //是否强制开启熔断 "forceOpen": false, //是否强制关闭熔断 "forceClosed": false }, //是否启用fallback "fallback": { "enabled": true } } } } }
须要添加一个名字为hystrix的节点,里面的command节点才是咱们要关注的地方!
default,是默认的配置,针对全部的Command!若是说有某个特定的Command要单独配置,能够在command下面添加相应的命令节点便可。
其余配置项,都已经用注释的方式解释了。
下面这张动图模拟了订单服务从可用->不可用->可用的情形。
除了服务不可用,可能还有一种状况发生的几率会比较大,超时!
举个例子,有一个服务日常都是响应很快,忽然有一段时间不知道什么缘由,处理请求的速度慢了不少,这段时间内常常出现客户端等待很长的时间,甚至超时了。
当遇到这种状况的时候,通常都会设置一个超时时间,只要在这个时间内没有响应就认为是超时了!
能够经过下面的配置来完成超时的配置:
{ "hystrix": { "command": { "default": { "execution": { "timeout": { "enabled": true }, "isolation": { "strategy": "THREAD", "thread": { //超时时间 "timeoutInMilliseconds": 1000 } } }, } } } }
这里也只是介绍了几个比较经常使用和简单的功能,它还能够合并多个请求,缓存请求等诸多实用的功能。整体来讲,Steeltoe的熔断功能,用起来还算是比较简单,也比较灵活。
更多配置和说明能够参考官方文档。
本文的示例代码: