From 42082fbefaae8163cf1ab54ce5bcab0daa62cfc1 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 13 Dec 2025 17:38:49 +0800 Subject: [PATCH] :hammer: Reconfigured to use new discovery --- DysonNetwork.Develop/Program.cs | 2 + DysonNetwork.Develop/appsettings.json | 5 +- DysonNetwork.Drive/Program.cs | 2 + DysonNetwork.Drive/appsettings.json | 5 +- DysonNetwork.Insight/Program.cs | 2 + DysonNetwork.Insight/appsettings.json | 5 +- DysonNetwork.Pass/Program.cs | 2 + DysonNetwork.Pass/appsettings.json | 5 +- DysonNetwork.Ring/Program.cs | 2 + DysonNetwork.Ring/appsettings.json | 5 +- .../Configurator/ConfigureService.cs | 31 ++++++++ .../Http/KestrelConfiguration.cs | 2 +- .../Registry/ServiceRegistrar.cs | 74 ++++++++++++++++--- DysonNetwork.Sphere/Program.cs | 2 + DysonNetwork.Sphere/appsettings.json | 5 +- DysonNetwork.Zone/Program.cs | 2 + DysonNetwork.Zone/appsettings.json | 5 +- 17 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 DysonNetwork.Shared/Configurator/ConfigureService.cs diff --git a/DysonNetwork.Develop/Program.cs b/DysonNetwork.Develop/Program.cs index d9a1104..7b75b06 100644 --- a/DysonNetwork.Develop/Program.cs +++ b/DysonNetwork.Develop/Program.cs @@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.Configure(opts => { opts.Name = "develop"; }); + builder.ConfigureAppKestrel(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); diff --git a/DysonNetwork.Develop/appsettings.json b/DysonNetwork.Develop/appsettings.json index 4e9d68f..ce84170 100644 --- a/DysonNetwork.Develop/appsettings.json +++ b/DysonNetwork.Develop/appsettings.json @@ -10,7 +10,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_develop;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + "App": "Host=localhost;Port=5432;Database=dyson_develop;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "Registrar": "127.0.0.1:2379", + "Cache": "127.0.0.1:6379", + "Queue": "127.0.0.1:4222" }, "KnownProxies": [ "127.0.0.1", diff --git a/DysonNetwork.Drive/Program.cs b/DysonNetwork.Drive/Program.cs index c7245a2..21cb457 100644 --- a/DysonNetwork.Drive/Program.cs +++ b/DysonNetwork.Drive/Program.cs @@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.Configure(opts => { opts.Name = "drive"; }); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue); diff --git a/DysonNetwork.Drive/appsettings.json b/DysonNetwork.Drive/appsettings.json index 34349e1..9ee7fa1 100644 --- a/DysonNetwork.Drive/appsettings.json +++ b/DysonNetwork.Drive/appsettings.json @@ -10,7 +10,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + "App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "Registrar": "127.0.0.1:2379", + "Cache": "127.0.0.1:6379", + "Queue": "127.0.0.1:4222" }, "Authentication": { "Schemes": { diff --git a/DysonNetwork.Insight/Program.cs b/DysonNetwork.Insight/Program.cs index 9682800..726448d 100644 --- a/DysonNetwork.Insight/Program.cs +++ b/DysonNetwork.Insight/Program.cs @@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.Services.Configure(opts => { opts.Name = "insight"; }); + builder.AddServiceDefaults(); builder.ConfigureAppKestrel(builder.Configuration); diff --git a/DysonNetwork.Insight/appsettings.json b/DysonNetwork.Insight/appsettings.json index b2da6a0..d908ccf 100644 --- a/DysonNetwork.Insight/appsettings.json +++ b/DysonNetwork.Insight/appsettings.json @@ -10,7 +10,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_insight;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + "App": "Host=localhost;Port=5432;Database=dyson_insight;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "Registrar": "127.0.0.1:2379", + "Cache": "127.0.0.1:6379", + "Queue": "127.0.0.1:4222" }, "KnownProxies": [ "127.0.0.1", diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 8a37de8..b054d44 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -8,6 +8,8 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.Configure(opts => { opts.Name = "pass"; }); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); diff --git a/DysonNetwork.Pass/appsettings.json b/DysonNetwork.Pass/appsettings.json index d76eea3..d41dff7 100644 --- a/DysonNetwork.Pass/appsettings.json +++ b/DysonNetwork.Pass/appsettings.json @@ -10,7 +10,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + "App": "Host=localhost;Port=5432;Database=dyson_pass;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "Registrar": "127.0.0.1:2379", + "Cache": "127.0.0.1:6379", + "Queue": "127.0.0.1:4222" }, "Authentication": { "Schemes": { diff --git a/DysonNetwork.Ring/Program.cs b/DysonNetwork.Ring/Program.cs index 72258c3..9aa3044 100644 --- a/DysonNetwork.Ring/Program.cs +++ b/DysonNetwork.Ring/Program.cs @@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.Configure(opts => { opts.Name = "ring"; }); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); diff --git a/DysonNetwork.Ring/appsettings.json b/DysonNetwork.Ring/appsettings.json index 9e44c5d..94e33ba 100644 --- a/DysonNetwork.Ring/appsettings.json +++ b/DysonNetwork.Ring/appsettings.json @@ -9,7 +9,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_ring;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + "App": "Host=localhost;Port=5432;Database=dyson_ring;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "Registrar": "127.0.0.1:2379", + "Cache": "127.0.0.1:6379", + "Queue": "127.0.0.1:4222" }, "Notifications": { "Push": { diff --git a/DysonNetwork.Shared/Configurator/ConfigureService.cs b/DysonNetwork.Shared/Configurator/ConfigureService.cs new file mode 100644 index 0000000..736bbc0 --- /dev/null +++ b/DysonNetwork.Shared/Configurator/ConfigureService.cs @@ -0,0 +1,31 @@ +using DysonNetwork.Shared.Registry; +using Microsoft.Extensions.Configuration; +using System.Text; +using System.Text.Json; + +namespace DysonNetwork.Shared.Configurator; + +public class ConfigureService(ServiceRegistrar registrar) +{ + private readonly ServiceRegistrar _registrar = registrar; + + public async Task GetConfigurationsAsync() + { + var instance = await _registrar.GetServiceInstanceAsync("config", "http"); + using var client = new HttpClient(); + var response = await client.GetStringAsync($"http://{instance}/config"); + var json = JsonDocument.Parse(response); + return json; + } + + public async Task ConfigureAppAsync(IConfigurationBuilder builder) + { + var configs = await GetConfigurationsAsync(); + if (configs.RootElement.TryGetProperty("connection_strings", out var csElement)) + { + var csJson = csElement.ToString(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(csJson)); + builder.AddJsonStream(stream); + } + } +} diff --git a/DysonNetwork.Shared/Http/KestrelConfiguration.cs b/DysonNetwork.Shared/Http/KestrelConfiguration.cs index c2f1790..79ecc57 100644 --- a/DysonNetwork.Shared/Http/KestrelConfiguration.cs +++ b/DysonNetwork.Shared/Http/KestrelConfiguration.cs @@ -23,7 +23,7 @@ public static class KestrelConfiguration if (enableGrpc) { // gRPC - var grpcPort = int.Parse(configuration.GetValue("GRPC_PORT", "5001")); + var grpcPort = int.Parse(configuration.GetValue("GRPC_PORT", "5000")); options.ListenAnyIP(grpcPort, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; diff --git a/DysonNetwork.Shared/Registry/ServiceRegistrar.cs b/DysonNetwork.Shared/Registry/ServiceRegistrar.cs index 8f6e55f..77aa6d7 100644 --- a/DysonNetwork.Shared/Registry/ServiceRegistrar.cs +++ b/DysonNetwork.Shared/Registry/ServiceRegistrar.cs @@ -3,6 +3,7 @@ using Etcdserverpb; using Google.Protobuf; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; namespace DysonNetwork.Shared.Registry; @@ -12,14 +13,21 @@ public class ServiceRegistrar(EtcdClient etcd) private string? _serviceKey; private long _leaseId; private long _ttlSeconds; + private readonly Dictionary _roundRobinCounters = new(); /// /// Register the service in etcd with a TTL lease. /// - public async Task RegisterAsync(string serviceName, string servicePart, string instanceId, string host, int port, long ttlSeconds = 30) + public async Task RegisterAsync( + string serviceName, + string servicePart, + string instanceId, + string host, + int port, + long ttlSeconds = 30) { _ttlSeconds = ttlSeconds; - _serviceKey = $"/services/{serviceName}/${servicePart}/{instanceId}"; + _serviceKey = $"/services/{serviceName}/{servicePart}/{instanceId}"; var serviceValue = $"{host}:{port}"; // Create and store TTL lease @@ -70,20 +78,66 @@ public class ServiceRegistrar(EtcdClient etcd) } } } + + /// + /// Get all service instances for a specific service name and part. + /// + public async Task> GetServiceInstancesAsync(string serviceName, string servicePart) + { + var prefix = $"/services/{serviceName}/{servicePart}/"; + var request = new RangeRequest + { + Key = ByteString.CopyFromUtf8(prefix), + RangeEnd = ByteString.CopyFromUtf8(prefix + "\0") + }; + var response = await etcd.GetAsync(request); + var instances = response.Kvs.Select(kv => kv.Value.ToStringUtf8()).ToList(); + return instances; + } + + /// + /// Get a single service instance with load balancing (round-robin). + /// + public async Task GetServiceInstanceAsync(string serviceName, string servicePart) + { + var instances = await GetServiceInstancesAsync(serviceName, servicePart); + if (instances.Count == 0) + throw new InvalidOperationException($"No instances found for service '{serviceName}' part '{servicePart}'"); + var key = $"{serviceName}/{servicePart}"; + if (!_roundRobinCounters.ContainsKey(key)) + _roundRobinCounters[key] = 0; + var instance = instances[_roundRobinCounters[key] % instances.Count]; + _roundRobinCounters[key] = (_roundRobinCounters[key] + 1) % int.MaxValue; + return instance; + } } -public class ServiceRegistrarHostedService(ServiceRegistrar registrar, IConfiguration config) : IHostedService +public sealed class ServiceRegistrationOptions { + public string Name { get; set; } = null!; + public string Host { get; set; } = "127.0.0.1"; + public string InstanceId { get; set; } = Guid.NewGuid().ToString("N"); +} + +public class ServiceRegistrarHostedService( + ServiceRegistrar registrar, + IConfiguration configuration, + IOptions options +) + : IHostedService +{ + private readonly ServiceRegistrationOptions _opts = options.Value; + public async Task StartAsync(CancellationToken cancellationToken) { - var name = config["Service:Name"]; - var host = config["Service:Host"]; - var grpcPort = int.Parse(config["Service:GrpcPort"]!); - var httpPort = int.Parse(config["Service:HttpPort"]!); - var instanceId = config["Service:InstanceId"] ?? Guid.NewGuid().ToString("N"); + var grpcPort = int.Parse(configuration.GetValue("GRPC_PORT", "5000")); + await registrar.RegisterAsync(_opts.Name, "grpc", _opts.InstanceId, _opts.Host, grpcPort); - await registrar.RegisterAsync(name, "grpc", instanceId, host, grpcPort); - await registrar.RegisterAsync(name, "http", instanceId, host, httpPort); + var httpPorts = configuration.GetValue("HTTP_PORTS", "6000") + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(p => int.Parse(p.Trim())) + .ToArray(); + await registrar.RegisterAsync(_opts.Name, "http", _opts.InstanceId, _opts.Host, httpPorts.First()); } public async Task StopAsync(CancellationToken cancellationToken) diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index f820430..c66ab61 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.Configure(opts => { opts.Name = "sphere"; }); + // Configure Kestrel and server options builder.ConfigureAppKestrel(builder.Configuration); diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json index a761e3f..04123c6 100644 --- a/DysonNetwork.Sphere/appsettings.json +++ b/DysonNetwork.Sphere/appsettings.json @@ -10,7 +10,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_sphere;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + "App": "Host=localhost;Port=5432;Database=dyson_sphere;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "Registrar": "127.0.0.1:2379", + "Cache": "127.0.0.1:6379", + "Queue": "127.0.0.1:4222" }, "GeoIp": { "DatabasePath": "./Keys/GeoLite2-City.mmdb" diff --git a/DysonNetwork.Zone/Program.cs b/DysonNetwork.Zone/Program.cs index dc8dd7a..873e241 100644 --- a/DysonNetwork.Zone/Program.cs +++ b/DysonNetwork.Zone/Program.cs @@ -10,6 +10,8 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.Services.Configure(opts => { opts.Name = "zone"; }); + builder.ConfigureAppKestrel(builder.Configuration); builder.Services.AddRazorPages(); diff --git a/DysonNetwork.Zone/appsettings.json b/DysonNetwork.Zone/appsettings.json index d816d41..f261a00 100644 --- a/DysonNetwork.Zone/appsettings.json +++ b/DysonNetwork.Zone/appsettings.json @@ -8,7 +8,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "App": "Host=localhost;Port=5432;Database=dyson_zone;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60" + "App": "Host=localhost;Port=5432;Database=dyson_zone;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60", + "Registrar": "127.0.0.1:2379", + "Cache": "127.0.0.1:6379", + "Queue": "127.0.0.1:4222" }, "KnownProxies": [ "127.0.0.1",