定义一个 Hystrix 豪猪哥,实现net core自定义服务降级和服务熔断。

定义一个 Hystrix 豪猪哥,实现net core自定义超时和重试。豪猪这种方式因为没办法针对每一个用户做到熔断的实现。所以分开写了,把流控放在下半段里面单独写了一个中间件。

HystrixCommand.cs  定义

  1 /// <summary>
  2     /// 定义一个 Hystrix 豪猪哥,实现net core自定义服务降级(只有超时和重试两种策略),熔断策略放到中间件里面实现。
  3     /// </summary>
  4     /// <typeparam name="T"></typeparam>
  5     public class HystrixCommand<T> where T : ControllerBase
  6     {
  7         private ILogger<HystrixCommand<T>> _logger;
  8 
  9         private readonly T _controllerBase;
 10 
 11         /// <summary>
 12         /// 
 13         /// </summary>
 14         /// <param name="logger"></param>
 15         /// <param name="controllerBase"></param>
 16         public HystrixCommand(ILogger<HystrixCommand<T>> logger, T controllerBase)
 17         {
 18             this._logger = logger;
 19             this._controllerBase = controllerBase;
 20         }
 21 
 22         const string CURRENT_URL = "CURRENT_URL";
 23         const string RETRY_CURRENT_TIME = "RETRY_CURRENT_TIME";
 24 
 25         /// <summary>
 26         /// 超时策略
 27         /// </summary>
 28         /// <typeparam name="Result"></typeparam>
 29         /// <param name="timeOutMilliseconds">执行超过多少毫秒则认为超时(0表示不检测超时)</param>
 30         /// <returns></returns>
 31         public Policy<Result> TimeoutPolicy<Result>(int timeOutMilliseconds)
 32         {
 33             Policy<Result> policy = null;
 34             if (timeOutMilliseconds > 0)
 35             {//是否启用执行超时的检查
 36                 policy = Policy<Result>
 37                                     .Handle<TimeoutRejectedException>()
 38                                     .Fallback(default(Result), onFallback: (result, context) =>
 39                                     {
 40                                         //超时 TimeOutMilliseconds 毫秒之后的降级处理
 41                                         throw new Exception("time out", result.Exception);
 42                                     })
 43                                     .Wrap(Policy.Timeout(
 44                                             TimeSpan.FromMilliseconds(timeOutMilliseconds)
 45                                             , Polly.Timeout.TimeoutStrategy.Pessimistic
 46                                             , onTimeout: (context, timeSpan, task, exception) =>
 47                                             {
 48                                                 string url = context[CURRENT_URL] as string;
 49                                                 //超时
 50                                                 this._logger.LogError($"{url} => time out, {exception}");
 51                                             }
 52                                         )
 53                                     )
 54                                     ;
 55             }
 56             return policy ?? Policy.NoOp<Result>();
 57         }
 58         /// <summary>
 59         /// 重试策略
 60         /// </summary>
 61         /// <typeparam name="Result"></typeparam>
 62         /// <param name="retryMaxTimes">最多重试几次,如果为0则不重试</param>
 63         /// <param name="retryIntervalMilliseconds">重试间隔的毫秒数</param>
 64         /// <returns></returns>
 65         public Policy<Result> RetryPolicy<Result>(int retryMaxTimes, int retryIntervalMilliseconds)
 66         {
 67             Policy<Result> policy = null;
 68             if (retryMaxTimes > 0)
 69             {
 70                 policy = Policy<Result>
 71                                     .Handle<Exception>()
 72                                     .Fallback(default(Result), onFallback: (result, context) =>
 73                                      {
 74                                          //重试 RetryMaxTimes 次之后的降级处理
 75                                          throw new Exception("retry failed", result.Exception);
 76                                      })
 77                                 ;
 78                 if (retryIntervalMilliseconds > 0)
 79                 {
 80                     policy = policy.Wrap(Policy<Result>
 81                                                     .Handle<Exception>()
 82                                                     .WaitAndRetry(
 83                                                         retryMaxTimes
 84                                                         , retryTime => TimeSpan.FromMilliseconds(retryIntervalMilliseconds)
 85                                                         , onRetry: (result, timeSpan, repeat, context) =>
 86                                                          {
 87                                                              context[RETRY_CURRENT_TIME] = repeat;
 88                                                              string url = context[CURRENT_URL] as string;
 89                                                              //重试
 90                                                              this._logger.LogError($"{url} => retry {repeat}");
 91                                                          }
 92                                                     )
 93                                         )
 94                                     ;
 95                 }
 96                 else
 97                 {
 98                     policy = policy.Wrap(Policy<Result>
 99                                             .Handle<Exception>()
100                                             .Retry(
101                                                 retryMaxTimes
102                                                 , onRetry: (ex, repeat, context) =>
103                                                 {
104                                                     context[RETRY_CURRENT_TIME] = repeat;
105                                                     string url = context[CURRENT_URL] as string;
106                                                     //重试
107                                                     this._logger.LogError($"{url} => retry {repeat}");
108                                                 }
109                                             )
110                                         )
111                                     ;
112                 }
113             }
114             return policy ?? Policy.NoOp<Result>();
115         }
116 
117         /// <summary>
118         /// 执行带有超时和重试的任务策略
119         /// </summary>
120         /// <typeparam name="Result"></typeparam>
121         /// <param name="func"></param>
122         /// <param name="timeOutMilliseconds">执行超过多少毫秒则认为超时(0表示不检测超时)</param>
123         /// <param name="retryMaxTimes">最多重试几次,如果为0则不重试</param>
124         /// <param name="retryIntervalMilliseconds">重试间隔的毫秒数</param>
125         /// <returns></returns>
126         public Result Execute<Result>(Func<Result> func, int timeOutMilliseconds, int retryMaxTimes = 0, int retryIntervalMilliseconds = 0)
127         {
128             var httpContext = this._controllerBase.HttpContext;
129 
130             Context context = new Context();
131 
132             context[CURRENT_URL] = httpContext.Request.GetDisplayUrl();
133             context[RETRY_CURRENT_TIME] = 0;
134 
135             Policy<Result> policy = Policy.NoOp<Result>();//创建一个空的Policy
136 
137             #region 超时
138             policy = policy.Wrap(this.TimeoutPolicy<Result>(timeOutMilliseconds));
139             #endregion
140 
141             #region 重试
142             policy = policy.Wrap(this.RetryPolicy<Result>(retryMaxTimes, retryIntervalMilliseconds));
143             #endregion
144 
145             return policy.Execute(context =>
146             {
147                 //重试 需要按照下面这种方式才能执行多次。
148                 int retryCurrentTime = (int)context[RETRY_CURRENT_TIME];
149                 do
150                 {
151                     return func();
152                 } while (retryMaxTimes > retryCurrentTime);
153             }, context);
154         }
155 
156     }
HystrixCommand.cs

Startup.cs 注入

services.AddTransient(typeof(HystrixCommand<>));

Controller.cs -> action 使用

 1         /// <summary>
 2         /// 例子只能实现超时和重试的策略
 3         /// </summary>
 4         /// <param name="hystrixCommand"></param>
 5         /// <returns></returns>
 6         public string Test([FromServices] HystrixCommand<CheckController> hystrixCommand)
 7         {
 8             return hystrixCommand.Execute<string>(() =>
 9             {
10                 return "ok";
11             }, 500, 3, 1000);//超时 ok //重试 ok //重试间隔 ok
12         }
Controller.cs

 


 下面才是针对每一个用户做到服务器端熔断的实现代码。

通过middleware可以实现针对用户级别的流控。感谢张浩帮忙。

CircuitBreakerMiddleware.cs

 1 /// <summary>
 2     /// net core 下面自定义熔断器中间件
 3     /// </summary>
 4     public class CircuitBreakerMiddleware : IDisposable
 5     {
 6         private readonly RequestDelegate _next;
 7         private readonly ConcurrentDictionary<string, AsyncPolicy> _asyncPolicyDict = null;
 8         private readonly ILogger<CircuitBreakerMiddleware> _logger;
 9         private readonly IConfiguration _configuration;
10 
11         public CircuitBreakerMiddleware(RequestDelegate next, ILogger<CircuitBreakerMiddleware> logger, IConfiguration configuration)
12         {
13             this._next = next;
14             this._logger = logger;
15             this._configuration = configuration;//未来url的熔断规则可以从config文件里读取,增加灵活性
16             logger.LogInformation($"{nameof(CircuitBreakerMiddleware)}.ctor()");
17             this._asyncPolicyDict = new ConcurrentDictionary<string, AsyncPolicy>(Environment.ProcessorCount, 31);
18         }
19 
20         public async Task InvokeAsync(HttpContext context)
21         {
22             var request = context.Request;
23             string httpMethod = request.Method;
24             string pathAndQuery = request.GetEncodedPathAndQuery();
25             var asyncPolicy = this._asyncPolicyDict.GetOrAdd(string.Concat(httpMethod, pathAndQuery), key =>
26             {
27                 return Policy.Handle<Exception>().CircuitBreakerAsync(3, TimeSpan.FromSeconds(10));
28             });
29             try
30             {
31                 await asyncPolicy.ExecuteAsync(async () => await this._next(context));
32             }
33             catch (BrokenCircuitException ex)
34             {
35                 this._logger.LogError($"{nameof(BrokenCircuitException)}.InnerException.Message:{ex.InnerException.Message}");
36                 var response = context.Response;
37                 response.StatusCode = (int)HttpStatusCode.BadRequest;
38                 response.ContentType = "text/plain; charset=utf-8";
39                 await response.WriteAsync("Circuit Broken o(╥﹏╥)o");
40             }
41 
42             //var endpoint = context.GetEndpoint();
43             //if (endpoint != null)
44             //{
45             //    var controllerActionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
46             //    var controllerName = controllerActionDescriptor.ControllerName;
47             //    var actionName = controllerActionDescriptor.ActionName;
48             //    if (string.Equals(controllerName, "WeatherForecast", StringComparison.OrdinalIgnoreCase)
49             //        && string.Equals(actionName, "Test", StringComparison.OrdinalIgnoreCase))
50             //    {//针对某一个控制器的某一个action,单独设置熔断
51             //        await Policy.Handle<Exception>().CircuitBreakerAsync(3, TimeSpan.FromSeconds(10)).ExecuteAsync(async () => await this._next(context));
52             //    }
53             //    else
54             //    {
55             //        await this._next(context);
56             //    }
57             //}
58         }
59 
60         public void Dispose()
61         {
62             this._asyncPolicyDict.Clear();
63             this._logger.LogInformation($"{nameof(CircuitBreakerMiddleware)}.Dispose()");
64         }
65     }
CircuitBreakerMiddleware.cs

Startup.cs

 1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 2         {
 3             if (env.IsDevelopment())
 4             {
 5                 app.UseDeveloperExceptionPage();
 6             }
 7 
 8             app.UseRouting();
 9 
10             //注意位置在Routing下面,UseEndpoints上面
11             app.UseMiddleware<CircuitBreakerMiddleware>();
12 
13             app.UseEndpoints(endpoints =>
14             {
15                 endpoints.MapControllers();
16             });
17         }
Startup.cs

WeatherForecastController.cs

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.AspNetCore.Mvc;
 6 using Microsoft.Extensions.Logging;
 7 
 8 namespace CircuitBreakerDemo.Controllers
 9 {
10     [ApiController]
11     [Route("[controller]")]
12     public class WeatherForecastController : ControllerBase
13     {
14         private static readonly string[] Summaries = new[]
15         {
16             "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
17         };
18 
19         private static Random _Random = new Random();
20 
21         private readonly ILogger<WeatherForecastController> _logger;
22 
23         public WeatherForecastController(ILogger<WeatherForecastController> logger)
24         {
25             _logger = logger;
26         }
27 
28         [HttpGet]
29         public IEnumerable<WeatherForecast> Get()
30         {
31             if (_Random.Next(Summaries.Length) % 3 == 0)
32             {
33                 throw new Exception("程序运行错误");
34             }
35 
36             return Enumerable.Range(1, 5).Select(index => new WeatherForecast
37             {
38                 Date = DateTime.Now.AddDays(index),
39                 TemperatureC = _Random.Next(-20, 55),
40                 Summary = Summaries[_Random.Next(Summaries.Length)]
41             })
42             .ToArray();
43         }
44 
45         [HttpGet("test")]
46         public string Test()
47         {
48             if (_Random.Next(Summaries.Length) % 3 == 0)
49             {
50                 throw new Exception("程序运行错误");
51             }
52             return Summaries[_Random.Next(Summaries.Length)];
53         }
54     }
55 }
WeatherForecastController.cs

 

 

 

 

 

posted @ 2020-09-25 17:39  —八戒—  阅读(321)  评论(0编辑  收藏  举报