• 谈谈Circuit Breaker在.NET Core中的简单应用


    前言

    由于微服务的盛行,不少公司都将原来细粒度比较大的服务拆分成多个小的服务,让每个小服务做好自己的事即可。

    经过拆分之后,就避免不了服务之间的相互调用问题!如果调用没有处理好,就有可能造成整个系统的瘫痪,好比说其中一些基础服务出现了故障,那么用到这些基础服务的地方都是要做一定的处理的,不能让它们出现大面积的瘫痪!!!

    正常情况下的解决方案就要对服务进行熔断处理,不能因为提供方出现了问题就让调用方也废了。

    熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施。

    对于这个问题,Steeltoe的Circuit Breaker是一个不错的选择。本文的示例代码也是基于它的。

    Steeltoe的Circuit Breaker

    Steeltoe是什么呢?Steeltoe可以说是构建微服务的一个解决方案吧。具体的可以访问它的官网:

    http://steeltoe.io/

    回归正题,先来看看官方对Circuit Breaker的描述:

    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}");
        }
    
    }
    

    这里有几个地方要注意:

    1. 构造函数一定要有IHystrixCommandOptions这个参数
    2. RunAsync是真正执行调用的地方
    3. RunFallbackAsync是由于某些原因不能拿到返回结果时会执行的地方,所谓的优雅降级。

    接下来要做的是在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的熔断功能,用起来还算是比较简单,也比较灵活。

    更多配置和说明可以参考官方文档

    本文的示例代码:

    CircuitBreakerDemo

  • 相关阅读:
    简单获取input file 选中的图片,并在一个div的img里面赋值src实现预览图片
    MVC4方法行为过滤器例子(用户登录)
    MVC4过滤器
    MVC4 @RenderBody、@RenderSection、@RenderPage、Html.RenderPartial、Html.RenderAction的作用和区别
    FPGA之外,了解一下中断
    滤波器中的窗口
    关于有限脉冲响应滤波器
    分布式算法原理
    学习FPGA过程中的理论知识
    关于硬件实现FFT逆运算
  • 原文地址:https://www.cnblogs.com/catcher1994/p/8975192.html
Copyright © 2020-2023  润新知