🔨 Reconfigured to use new discovery

This commit is contained in:
2025-12-13 17:38:49 +08:00
parent bc3d030a1e
commit 42082fbefa
17 changed files with 138 additions and 18 deletions

View File

@@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); builder.AddServiceDefaults();
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "develop"; });
builder.ConfigureAppKestrel(builder.Configuration); builder.ConfigureAppKestrel(builder.Configuration);
builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppServices(builder.Configuration);

View File

@@ -10,7 +10,10 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "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": [ "KnownProxies": [
"127.0.0.1", "127.0.0.1",

View File

@@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); builder.AddServiceDefaults();
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "drive"; });
// Configure Kestrel and server options // Configure Kestrel and server options
builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue); builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue);

View File

@@ -10,7 +10,10 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "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": { "Authentication": {
"Schemes": { "Schemes": {

View File

@@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "insight"; });
builder.AddServiceDefaults(); builder.AddServiceDefaults();
builder.ConfigureAppKestrel(builder.Configuration); builder.ConfigureAppKestrel(builder.Configuration);

View File

@@ -10,7 +10,10 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "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": [ "KnownProxies": [
"127.0.0.1", "127.0.0.1",

View File

@@ -8,6 +8,8 @@ var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); builder.AddServiceDefaults();
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "pass"; });
// Configure Kestrel and server options // Configure Kestrel and server options
builder.ConfigureAppKestrel(builder.Configuration); builder.ConfigureAppKestrel(builder.Configuration);

View File

@@ -10,7 +10,10 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "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": { "Authentication": {
"Schemes": { "Schemes": {

View File

@@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); builder.AddServiceDefaults();
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "ring"; });
// Configure Kestrel and server options // Configure Kestrel and server options
builder.ConfigureAppKestrel(builder.Configuration); builder.ConfigureAppKestrel(builder.Configuration);

View File

@@ -9,7 +9,10 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "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": { "Notifications": {
"Push": { "Push": {

View File

@@ -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<JsonDocument> 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);
}
}
}

View File

@@ -23,7 +23,7 @@ public static class KestrelConfiguration
if (enableGrpc) if (enableGrpc)
{ {
// gRPC // gRPC
var grpcPort = int.Parse(configuration.GetValue("GRPC_PORT", "5001")); var grpcPort = int.Parse(configuration.GetValue("GRPC_PORT", "5000"));
options.ListenAnyIP(grpcPort, listenOptions => options.ListenAnyIP(grpcPort, listenOptions =>
{ {
listenOptions.Protocols = HttpProtocols.Http2; listenOptions.Protocols = HttpProtocols.Http2;

View File

@@ -3,6 +3,7 @@ using Etcdserverpb;
using Google.Protobuf; using Google.Protobuf;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace DysonNetwork.Shared.Registry; namespace DysonNetwork.Shared.Registry;
@@ -12,14 +13,21 @@ public class ServiceRegistrar(EtcdClient etcd)
private string? _serviceKey; private string? _serviceKey;
private long _leaseId; private long _leaseId;
private long _ttlSeconds; private long _ttlSeconds;
private readonly Dictionary<string, int> _roundRobinCounters = new();
/// <summary> /// <summary>
/// Register the service in etcd with a TTL lease. /// Register the service in etcd with a TTL lease.
/// </summary> /// </summary>
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; _ttlSeconds = ttlSeconds;
_serviceKey = $"/services/{serviceName}/${servicePart}/{instanceId}"; _serviceKey = $"/services/{serviceName}/{servicePart}/{instanceId}";
var serviceValue = $"{host}:{port}"; var serviceValue = $"{host}:{port}";
// Create and store TTL lease // Create and store TTL lease
@@ -70,20 +78,66 @@ public class ServiceRegistrar(EtcdClient etcd)
} }
} }
} }
/// <summary>
/// Get all service instances for a specific service name and part.
/// </summary>
public async Task<List<string>> 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;
} }
public class ServiceRegistrarHostedService(ServiceRegistrar registrar, IConfiguration config) : IHostedService /// <summary>
/// Get a single service instance with load balancing (round-robin).
/// </summary>
public async Task<string> 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 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<ServiceRegistrationOptions> options
)
: IHostedService
{
private readonly ServiceRegistrationOptions _opts = options.Value;
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
var name = config["Service:Name"]; var grpcPort = int.Parse(configuration.GetValue("GRPC_PORT", "5000"));
var host = config["Service:Host"]; await registrar.RegisterAsync(_opts.Name, "grpc", _opts.InstanceId, _opts.Host, grpcPort);
var grpcPort = int.Parse(config["Service:GrpcPort"]!);
var httpPort = int.Parse(config["Service:HttpPort"]!);
var instanceId = config["Service:InstanceId"] ?? Guid.NewGuid().ToString("N");
await registrar.RegisterAsync(name, "grpc", instanceId, host, grpcPort); var httpPorts = configuration.GetValue("HTTP_PORTS", "6000")
await registrar.RegisterAsync(name, "http", instanceId, host, httpPort); .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) public async Task StopAsync(CancellationToken cancellationToken)

View File

@@ -9,6 +9,8 @@ var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); builder.AddServiceDefaults();
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "sphere"; });
// Configure Kestrel and server options // Configure Kestrel and server options
builder.ConfigureAppKestrel(builder.Configuration); builder.ConfigureAppKestrel(builder.Configuration);

View File

@@ -10,7 +10,10 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "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": { "GeoIp": {
"DatabasePath": "./Keys/GeoLite2-City.mmdb" "DatabasePath": "./Keys/GeoLite2-City.mmdb"

View File

@@ -10,6 +10,8 @@ var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); builder.AddServiceDefaults();
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "zone"; });
builder.ConfigureAppKestrel(builder.Configuration); builder.ConfigureAppKestrel(builder.Configuration);
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();

View File

@@ -8,7 +8,10 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "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": [ "KnownProxies": [
"127.0.0.1", "127.0.0.1",