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

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

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

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

c# 复制代码
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 的服务配置终结点:

json 复制代码
  "Services": {
    "todo": {
      "http": [
        "http://localhost:5124"
      ]
    }
  }

然后使用服务发现:

c# 复制代码
#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

运行程序后将会发现成功执行:

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

实现核心接口IServiceEndPointProvider

c# 复制代码
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:

c# 复制代码
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

c# 复制代码
public static IServiceCollection AddConsulServiceEndpointProvider(this IServiceCollection services)
{
	services.AddServiceDiscoveryCore();
	services.AddSingleton<IServiceEndPointProviderFactory, ConsulServiceEndPointProviderFactory>();
	return services;
}

最后添加一行代码 :

c# 复制代码
// 使用Microsoft.Extensions.ServiceDiscovery实现负载均衡
builder.Services.AddServiceDiscovery()
    .AddConfigurationServiceEndPointResolver() //config
    .AddConsulServiceEndpointProvider(); //consul

下面是Consul中注册完成的服务:

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

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

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

相关推荐
中标智研(深圳)6 天前
国家标准审查阶段需要注意的有哪些?还有什么其他要求?
服务发现·业界资讯
磐石区7 天前
gRPC etcd 服务注册与发现、自定义负载均衡
服务发现·负载均衡·etcd·grpc·picker
DEARM LINER8 天前
dubbo 服务消费原理分析之应用级服务发现
spring boot·架构·服务发现·dubbo·safari
嘟嘟 嘟嘟嘟11 天前
prometheus基于consul的服务发现
服务发现·prometheus·consul
zhuyasen14 天前
sponge创建的服务与dtm连接使用etcd、consul、nacos进行服务注册与发现
微服务·rpc·golang·服务发现·etcd·consul
嘟嘟 嘟嘟嘟15 天前
prometheus基于文件的服务发现
服务发现·prometheus
闫铁娃22 天前
Go反射四讲---第二讲:反射与结构体,使用反射如何操纵结构体?
开发语言·后端·微服务·云原生·golang·服务发现
heromps24 天前
探索微服务架构中的动态服务发现与调用:使用 Nacos 与 Spring Cloud OpenFeign 打造高效订单管理系统
微服务·架构·服务发现
zx0105zx1 个月前
Prometheus 服务发现
服务发现·prometheus
vibag1 个月前
基于Netty的RPC框架
网络协议·微服务·rpc·架构·服务发现