Dotnet微服务:Steeltoe使用Hystrix熔断器
一,准备内容
熔断:微服务架构不可避免需要进行服务间的通信,当某个服务调用另一个服务,当被调用服务出现状况,如宕机、网络延迟等状况不能做出及时响应时,切断该服务,避免当前服务不断进行重试而可能导致的系统“雪崩”或大量的超时等待影响服务性能。
降级:触发熔断后,在一个可配置时间内所有对被熔断服务的请求都将被做请求降级处理,以提供一个有损的服务,保证整个系统的稳定性和可以性。
hystrix:Netflix API 团队开发,通过分离服务的调用点,阻止错误在各个系统的传播,并且提供了错误回调机制,被设计用来做了下面几件事:
- 保护系统间的调用延时以及错误,特别是通过第三方的工具的网络调用
- 阻止错误在分布式系统之前的传播
- 快速失败和迅速恢复
- 错误回退和优雅的服务降级
Steeltoe.CircuitBreaker.Hystrix:Dotnet平台上Hystrix的实现包。官方文档
Hystrix熔断触发机制:
1,自动触发:通过配置Hystrix以下参数实现自动触发
//是否根据断路器的运行状态开启断路器短路 circuitBreaker.enabled //sleepWindowInMilliseconds时间窗口内断路器短路的最小请求值 circuitBreaker.requestVolumeThreshold //触发断路器的周期时间值 circuitBreaker.sleepWindowInMilliseconds //断路器的sleepWindowInMilliseconds窗口期内能够容忍的错误百分比阈值 circuitBreaker.errorThresholdPercentage
理解:在circuitBreaker.sleepWindowInMilliseconds时间内最少有circuitBreaker.requestVolumeThreshold个请求并且请求异常数量达到总数的circuitBreaker.errorThresholdPercentage百分比时触发熔断
更多配置信息请参考:https://github.com/Netflix/Hystrix/wiki/Configuration
2,强制触发
//强制关闭 circuitBreaker.forceClosed //强制打开 circuitBreaker.forceOpen
二,使用Steeltoe.CircuitBreaker.Hystrix
下面使用实例场景模拟说明Steeltoe.CircuitBreaker.Hystrix的使用和作用。
有二个服务,用户管理服务及位置管理服务,用管理服务通过IHttpclientFactory向位置管理服务查询某个用户家庭地址的GPS坐标,位置管理服务出现状况不能及时回应,通过Hystrix的Failback返回一个默认值。
1,安装Nuget安装包:Steeltoe.CircuitBreaker.Hystrix
2,用户管理服务使用IHttpclientFactory访问位置管理服务:参考之前的文章:使用IHttpclientFactory实现服务之间的通信
位置管理服务:添加获取位置接口:
[HttpPost("GetPosition")] public object GetPosition([FromBody] string address) { return new { Lat = 127.21455, Lng = 52.4878997, Address = address }; }
用户管理服务:创建类型化客户端:
public class OrderServiceRemoting { public HttpClient client { get; } public OrderServiceRemoting(HttpClient client) { client.BaseAddress = new Uri("http://eureka-order-service"); client.DefaultRequestHeaders.Add("OrderServiceHeader", "test"); this.client = client; } public async Task<string> GetOrderAsync(string name) { var request = new HttpRequestMessage(HttpMethod.Post, "api/order/getorder"); request.Content = new StringContent(JsonConvert.SerializeObject("test"), System.Text.Encoding.UTF8, "application/json"); using var ret =await client.SendAsync(request); ret.EnsureSuccessStatusCode(); return await ret.Content.ReadAsStringAsync(); } }
用户管理服务:添加类型化客户端 Startup.ConfigureServices
services.AddHttpClient("positionservice").AddServiceDiscovery().AddTypedClient<PositionServiceRemoting>();
用户管理服务:使用:
[Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { private readonly PositionServiceRemoting positionServiceRemoting; public UserController(PositionServiceRemoting positionServiceRemoting) { this.positionServiceRemoting = positionServiceRemoting; } [HttpGet("GetAddress")] public async Task<PostionModel> GetAddressAsync(string address) { return await positionServiceRemoting.GetPositionAsync(address); } }
访问:
3,创建一个Hystrix命令,命令类必需继承自HystrixCommand<ReslutType>。ReslutType为预计返回类型。
public interface IPositionServiceCommand { Task<PostionModel> GetAddressAsync(string address); } public class PositionServiceCommand:HystrixCommand<PostionModel>, IPositionServiceCommand { PositionServiceRemoting positionServiceRemoting; ILogger<PositionServiceCommand> log; private string address; public PositionServiceCommand( IHystrixCommandOptions options, PositionServiceRemoting positionServiceRemoting, ILogger<PositionServiceCommand> log) :base(options) { this.positionServiceRemoting = positionServiceRemoting; this.log = log; } public Task<PostionModel> GetAddressAsync(string address) { this.address = address; return ExecuteAsync(); } protected override Task<PostionModel> RunAsync() { return this.positionServiceRemoting.GetPositionAsync(address); } protected override Task<PostionModel> RunFallbackAsync() { PostionModel model = new PostionModel() { Address = this.address, Lat = 0, Lng = 0 }; log.LogInformation("进入fallback进行降级处理"); return Task.FromResult(model); } }
添加该命令
Startup.ConfigureServices
services.AddHystrixCommand<IPositionServiceCommand, PositionServiceCommand>("positionHystrix", Configuration);
使用命令访问资源
private readonly IPositionServiceCommand positionServiceCommand; public UserController(IPositionServiceCommand positionServiceCommand) { this.positionServiceCommand = positionServiceCommand; } [HttpGet("GetAddress")] public async Task<PostionModel> GetAddressAsync(string address) { return await positionServiceCommand.GetAddressAsync(address); }
配置断路器参数,将最小请求数改为4,系统默认值为20。意味着在默认时间内(10秒)如果有4次以上的请求并有50%的请求异常数则开启熔断。
Appsettings.json
"hystrix": { "command": { "positionHystrix": { "circuitBreaker": { "requestVolumeThreshold":4 } } } }
三,合并命令请求
应用场景:在一个时间窗口内请求同一服务的数量很多,可以将这些请求合并成一个请求,减少请求数量,优化资源占用。
要求:被调用方需要提供批量返回结果的接口
1,位置管理服务新建批量返回结果
[HttpPost("GetPositions")] public object GetPositions([FromBody] List<string> address) { List<object> list = new List<object>(); if (address != null) { address.ForEach(r => { list.Add(new { Lat = 127.21455+1, Lng = 52.4878997+1, Address = r }); }); } return list; }
2,用户管理服务:IHttpclientFactory类型化客户端新增Getpositions的调用
public class PostionModel { public decimal Lat { get; set; } public decimal Lng { get; set; } public string Address { get; set; } } public class PositionServiceRemoting { HttpClient httpClient { get; } readonly ILogger logger; public PositionServiceRemoting(HttpClient httpClient,ILogger <PositionServiceRemoting> logger) { httpClient.BaseAddress = new Uri("http://eureka-order-service"); this.httpClient = httpClient; this.logger = logger; } public async Task<PostionModel> GetPositionAsync(string address) { var request = new HttpRequestMessage(HttpMethod.Post, "api/position/GetPosition") { Content = new StringContent(JsonConvert.SerializeObject(address), System.Text.Encoding.UTF8, "application/json") }; using var ret = await httpClient.SendAsync(request); ret.EnsureSuccessStatusCode(); string retString= await ret.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<PostionModel>(retString); } public async Task<List<PostionModel>> GetPositionsAsync(List<string> address) { var request = new HttpRequestMessage(HttpMethod.Post, "api/position/GetPositions") { Content = new StringContent(JsonConvert.SerializeObject(address), System.Text.Encoding.UTF8, "application/json") }; using var ret = await httpClient.SendAsync(request); ret.EnsureSuccessStatusCode(); string retString = await ret.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<List<PostionModel>>(retString); } }
3,用户管理服务:为批量位置返回创建新的Hystrix命令
public class BatPositionServiceCommand : HystrixCommand<List<PostionModel>> { PositionServiceRemoting positionServiceRemoting; ILogger<BatPositionServiceCommand> log; ICollection<ICollapsedRequest<PostionModel, string>> requests; public BatPositionServiceCommand( IHystrixCommandGroupKey group, PositionServiceRemoting positionServiceRemoting, ICollection<ICollapsedRequest<PostionModel, string>> requests, ILogger<BatPositionServiceCommand> log) : base(group) { this.positionServiceRemoting = positionServiceRemoting; this.log = log; this.requests = requests; } protected override Task<List<PostionModel>> RunAsync() { return positionServiceRemoting.GetPositionsAsync(this.requests.ToList().Select(r=>r.Argument).ToList()); } protected override Task<List<PostionModel>> RunFallbackAsync() { List<PostionModel> model = new List<PostionModel>(); this.requests.ToList().Select(r => r.Argument).ToList().ForEach(r => { model.Add(new PostionModel() { Address = r, Lat = 0, Lng = 0 }); }); log.LogInformation("进入fallback进行降级处理"); return Task.FromResult(model); } }
4,用户管理服务:新建Hystrix合并命令
public class PositionHystrixCollapser : HystrixCollapser<List<PostionModel>, PostionModel, string> { ILogger<PositionHystrixCollapser> logger; PositionServiceRemoting positionServiceRemoting; ILoggerFactory loggerFactory; public string Address { get; set; } public override string RequestArgument { get { return Address; } } public PositionHystrixCollapser( HystrixCollapserOptions options, PositionServiceRemoting positionServiceRemoting, ILogger<PositionHystrixCollapser> logger, ILoggerFactory loggerFactory ) : base(options) { this.logger = logger; this.loggerFactory = loggerFactory; this.positionServiceRemoting = positionServiceRemoting; } public async Task<PostionModel> GetPositionAsync(string address) { this.Address = address; return await this.ExecuteAsync(); } protected override HystrixCommand<List<PostionModel>> CreateCommand(ICollection<ICollapsedRequest<PostionModel, string>> requests) { var command = new BatPositionServiceCommand(HystrixCommandGroupKeyDefault.AsKey("BatPositionService"), positionServiceRemoting, requests,loggerFactory.CreateLogger<BatPositionServiceCommand>()); return command; } protected override void MapResponseToRequests(List<PostionModel> batchResponse, ICollection<ICollapsedRequest<PostionModel, string>> requests) { foreach (var req in requests) { var exsit = batchResponse.Where(r => r.Address == req.Argument).FirstOrDefault(); if (exsit != null) { req.Response = exsit; } } } }
5,添加命令及合并命令到容器
Startup.Configservices
services.AddHttpClient("positionservice").AddServiceDiscovery().AddTypedClient<PositionServiceRemoting>(); services.AddHystrixCollapser<PositionHystrixCollapser>("PostionCollapser", Configuration); services.AddHystrixCommand<IPositionServiceCommand, PositionServiceCommand>("positionHystrix", Configuration); services.AddHystrixCommand<BatPositionServiceCommand>("BatPositionService", Configuration);
Startup.Config
app.UseHystrixRequestContext();
6,使用:
readonly PositionHystrixCollapser positionHystrixCollapser; public UserController(IPositionServiceCommand positionServiceCommand,PositionHystrixCollapser positionHystrixCollapser) { this.positionServiceCommand = positionServiceCommand; this.positionHystrixCollapser = positionHystrixCollapser; } [HttpGet("GetAddress2")] public async Task<PostionModel> GetAddress2Async(string address) { return await positionHystrixCollapser.GetPositionAsync(address); }
7,配置参数
//批量请求队列最大请求数量 maxRequestsInBatch //时间窗口,在该窗口内的请求将会合并 timerDelayInMilliseconds
四,启用UI界面
1,安装Nuget包:Steeltoe.CircuitBreaker.Hystrix.MetricsEventsCore
2,启用Metrics
Startup.Configservices
services.AddHystrixMetricsStream(Configuration);
Startup.Config
app.UseMvc();
app.UseHystrixMetricsStream();
3,创建一个spring-boot项目,引用:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
配置
server: port: 8017 spring: application: name: Hystrix-Dashboard hystrix: dashboard: proxy-stream-allow-list: "*"
启动后打开地址:http://localhost:8017/hystrix,在流源输入行中输入用户管理服务地址:http://localhost:8013/hystrix/hystrix.stream
点击Monitor Stream