.NET服务发现(Microsoft.Extensions.ServiceDiscovery)集成Consul

  • .NET服务发现(Microsoft.Extensions.ServiceDiscovery)集成Consul已关闭评论
  • 51 次浏览
  • A+
所属分类:.NET技术
摘要

随着Aspire发布preview5的发布,Microsoft.Extensions.ServiceDiscovery随之更新,服务注册发现这个属于老掉牙的话题解决什么问题就不赘述了,这里主要讲讲Microsoft.Extensions.ServiceDiscovery(preview5)以及如何扩展其他的中间件的发现集成 .

随着Aspire发布preview5的发布,Microsoft.Extensions.ServiceDiscovery随之更新,

服务注册发现这个属于老掉牙的话题解决什么问题就不赘述了,这里主要讲讲Microsoft.Extensions.ServiceDiscovery(preview5)以及如何扩展其他的中间件的发现集成 .

Microsoft.Extensions.ServiceDiscovery官方默认提供的Config,DNS,YARP三种Provider,使用也比较简单 :

builder.Services.AddServiceDiscovery();  builder.Services.AddHttpClient<CatalogServiceClient>(static client =>     {         client.BaseAddress = new("http://todo");     });  builder.Services.ConfigureHttpClientDefaults(static http => {     // 全局对HttpClient启用服务发现     http.UseServiceDiscovery(); }); 

然后 appsettings.json 为名为 todo 的服务配置终结点:

  "Services": {     "todo": {       "http": [         "http://localhost:5124"       ]     }   } 

然后使用服务发现:

 #region 模拟服务端的todo接口:  var sampleTodos = new Todo[] {     new(1, "Walk the dog"),     new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),     new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),     new(4, "Clean the bathroom"),     new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2))) };  var todosApi = app.MapGroup("/todos"); todosApi.MapGet("/", () => sampleTodos); todosApi.MapGet("/{id}", (int id) =>     sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo         ? Results.Ok(todo)         : Results.NotFound()); #endregion  public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);  [JsonSerializable(typeof(Todo[]))] internal partial class AppJsonSerializerContext : JsonSerializerContext { }  #region 测试服务发现和负载  app.MapGet("/test", async (IHttpClientFactory clientFactory) => {     //这里服务发现将自动解析配置文件中的服务     var client = clientFactory.CreateClient("todo");     var response = await client.GetAsync("/todos");     var todos = await response.Content.ReadAsStringAsync();     return Results.Content(todos, contentType: "application/json"); });  #endregion  

运行程序后将会发现成功执行:
.NET服务发现(Microsoft.Extensions.ServiceDiscovery)集成Consul

当然对于这样写死配置的服务发现一点都不灵活,因此应运而生了 YARP和DNS这些Provider, 目前服务注册发现使用Consul的还是挺多的,当然还有很多其他的轮子就不赘述了,这里我们来扩展一个Consul的服务发现Provider :

实现核心接口IServiceEndPointProvider

internal class ConsulServiceEndPointProvider(ServiceEndPointQuery query, IConsulClient consulClient, ILogger logger)         : IServiceEndPointProvider, IHostNameFeature     {         const string Name = "Consul";         private readonly string _serviceName = query.ServiceName;         private readonly IConsulClient _consulClient = consulClient;         private readonly ILogger _logger = logger;          public string HostName => query.ServiceName;  #pragma warning disable CA1816 // Dispose 方法应调用 SuppressFinalize         public ValueTask DisposeAsync() => default;          public async ValueTask PopulateAsync(IServiceEndPointBuilder endPoints, CancellationToken cancellationToken)         {             var flag = ServiceNameParts.TryParse(_serviceName, out var serviceNameParts);             var sum = 0;             if (flag)             {                 var queryResult = await _consulClient.Health.Service(serviceNameParts.Host, string.Empty, true, cancellationToken);                 foreach (var serviceEntry in queryResult.Response)                 {                     var address = $"{serviceEntry.Service.Address}:{serviceEntry.Service.Port}";                     var isEndpoint = ServiceNameParts.TryCreateEndPoint(address, out var endPoint);                     if (isEndpoint)                     {                         ++sum;                         var serviceEndPoint = ServiceEndPoint.Create(endPoint!);                         serviceEndPoint.Features.Set<IServiceEndPointProvider>(this);                         serviceEndPoint.Features.Set<IHostNameFeature>(this);                         endPoints.EndPoints.Add(serviceEndPoint);                         _logger.LogInformation($"ConsulServiceEndPointProvider Found Service {_serviceName}:{address}");                     }                 }             }              if (sum == 0)             {                 _logger.LogWarning($"No ConsulServiceEndPointProvider were found for service '{_serviceName}' ('{HostName}').");             }         }          /// <inheritdoc/>         public override string ToString() => Name;     }  

实现 IServiceEndPointProviderFactory:

 internal class ConsulServiceEndPointProviderFactory(IConsulClient consulClient, ILogger<ConsulServiceEndPointProviderFactory> logger) : IServiceEndPointProviderFactory     {         private readonly IConsulClient _consulClient = consulClient;         private readonly ILogger<ConsulServiceEndPointProviderFactory> _logger = logger;          public bool TryCreateProvider(ServiceEndPointQuery query, [NotNullWhen(true)] out IServiceEndPointProvider? resolver)         {             resolver = new ConsulServiceEndPointProvider(query, _consulClient, _logger);             return true;         }     }  

接着扩展一下IServiceCollection

 public static IServiceCollection AddConsulServiceEndpointProvider(this IServiceCollection services) { 	services.AddServiceDiscoveryCore(); 	services.AddSingleton<IServiceEndPointProviderFactory, ConsulServiceEndPointProviderFactory>(); 	return services; }  

最后添加一行代码 :

// 使用Microsoft.Extensions.ServiceDiscovery实现负载均衡 builder.Services.AddServiceDiscovery()     .AddConfigurationServiceEndPointResolver() //config     .AddConsulServiceEndpointProvider(); //consul 

下面是Consul中注册完成的服务:
.NET服务发现(Microsoft.Extensions.ServiceDiscovery)集成Consul

然后我们请求 ./test 调用服务,观察调试日志,成功了!

.NET服务发现(Microsoft.Extensions.ServiceDiscovery)集成Consul

完整的代码:
https://github.com/vipwan/Biwen.Microsoft.Extensions.ServiceDiscovery.Consul

当然你也可以直接使用nuget引用 Biwen.Microsoft.Extensions.ServiceDiscovery.Consul 我已经发布到了nuget上 , 最后因为Aspire还在不停的迭代所以Biwen.Microsoft.Extensions.ServiceDiscovery.Consul后面还会存在一些变化, 前面的几个早期版本我都做了适配以最新的为准