定义一个 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 }
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 }
下面才是针对每一个用户做到服务器端熔断的实现代码。
通过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 }
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 }
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 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2014-09-25 Quartz.net官方开发指南[转]
2014-09-25 配置Windows server 2008文件同步[转]