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


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

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

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

    builder.Services.AddServiceDiscovery();
    
    builder.Services.AddHttpClient(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
    
    

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

    当然对于这样写死配置的服务发现一点都不灵活,因此应运而生了 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(this);
                            serviceEndPoint.Features.Set(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}').");
                }
            }
    
            /// 
            public override string ToString() => Name;
        }
    
    

    实现 IServiceEndPointProviderFactory:

    
    internal class ConsulServiceEndPointProviderFactory(IConsulClient consulClient, ILogger logger) : IServiceEndPointProviderFactory
        {
            private readonly IConsulClient _consulClient = consulClient;
            private readonly ILogger _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();
    	return services;
    }
    
    

    最后添加一行代码 :

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

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

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

    image

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

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

  • 相关阅读:
    简单聊聊羊了个羊
    原创工具14Finger-全能web指纹识别与分享平台
    JVM(尚硅谷)学习之GC日志分析
    【机器人算法】机器人运动学参数辨识/DH参数校准/DH参数辨识
    机器视觉知识讲的深不如讲的透
    现代图片性能优化及体验优化指南 - 懒加载及异步图像解码方案
    STC 51单片机42——汇编 定时器 舵机
    2022年6月电子学会Python等级考试试卷(二级)答案解析
    STM8应用笔记3.GPIO输出 (1)
    Matplotlib 是一个广泛用于 Python 数据可视化的库
  • 原文地址:https://www.cnblogs.com/vipwan/p/18129361