✨ Setup etcd helper and magic onion
This commit is contained in:
70
DysonNetwork.Shared/Etcd/EtcdService.cs
Normal file
70
DysonNetwork.Shared/Etcd/EtcdService.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using dotnet_etcd;
|
||||
using Etcdserverpb;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace DysonNetwork.Shared.Etcd;
|
||||
|
||||
public class EtcdService(string connectionString) : IEtcdService
|
||||
{
|
||||
private readonly EtcdClient _etcdClient = new(connectionString);
|
||||
private long _leaseId;
|
||||
private string? _serviceKey;
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
public async Task RegisterServiceAsync(string serviceName, string serviceAddress, int ttl = 15)
|
||||
{
|
||||
_serviceKey = $"/services/{serviceName}/{Guid.NewGuid()}";
|
||||
var leaseGrantResponse = await _etcdClient.LeaseGrantAsync(new LeaseGrantRequest { TTL = ttl });
|
||||
_leaseId = leaseGrantResponse.ID;
|
||||
|
||||
await _etcdClient.PutAsync(new PutRequest
|
||||
{
|
||||
Key = Google.Protobuf.ByteString.CopyFromUtf8(_serviceKey),
|
||||
Value = Google.Protobuf.ByteString.CopyFromUtf8(serviceAddress),
|
||||
Lease = _leaseId
|
||||
});
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (!_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _etcdClient.LeaseKeepAlive(new LeaseKeepAliveRequest { ID = _leaseId },
|
||||
_ => { }, _cts.Token);
|
||||
await Task.Delay(TimeSpan.FromSeconds(ttl / 3), _cts.Token);
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}, _cts.Token);
|
||||
}
|
||||
|
||||
public async Task UnregisterServiceAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_serviceKey))
|
||||
{
|
||||
await _etcdClient.DeleteRangeAsync(_serviceKey);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<string>> DiscoverServicesAsync(string serviceName)
|
||||
{
|
||||
var prefix = $"/services/{serviceName}/";
|
||||
var rangeResponse = await _etcdClient.GetRangeAsync(prefix);
|
||||
return rangeResponse.Kvs.Select(kv => kv.Value.ToStringUtf8()).ToList();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
if (_leaseId != 0)
|
||||
{
|
||||
_etcdClient.LeaseRevoke(new LeaseRevokeRequest { ID = _leaseId });
|
||||
}
|
||||
|
||||
_etcdClient.Dispose();
|
||||
}
|
||||
}
|
||||
46
DysonNetwork.Shared/Etcd/EtcdServiceExtensions.cs
Normal file
46
DysonNetwork.Shared/Etcd/EtcdServiceExtensions.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Grpc.Net.Client;
|
||||
using MagicOnion.Client;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DysonNetwork.Shared.Etcd
|
||||
{
|
||||
public static class EtcdServiceExtensions
|
||||
{
|
||||
public static IServiceCollection AddEtcdService(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var etcdConnectionString = configuration.GetConnectionString("Etcd");
|
||||
services.AddSingleton<IEtcdService>(new EtcdService(etcdConnectionString!));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMagicOnionService<TService>(this IServiceCollection services)
|
||||
where TService : class, MagicOnion.IService<TService>
|
||||
{
|
||||
services.AddSingleton(serviceProvider =>
|
||||
{
|
||||
var etcdService = serviceProvider.GetRequiredService<IEtcdService>();
|
||||
var serviceName = typeof(TService).Name.TrimStart('I'); // Convention: IMyService -> MyService
|
||||
|
||||
// Synchronously wait for service discovery (or handle asynchronously if preferred)
|
||||
var endpoints = etcdService.DiscoverServicesAsync(serviceName).GetAwaiter().GetResult();
|
||||
|
||||
if (!endpoints.Any())
|
||||
{
|
||||
throw new InvalidOperationException($"No endpoints found for MagicOnion service: {serviceName}");
|
||||
}
|
||||
|
||||
// For simplicity, use the first discovered endpoint
|
||||
var endpoint = endpoints.First();
|
||||
|
||||
var channel = GrpcChannel.ForAddress(endpoint);
|
||||
return MagicOnionClient.Create<TService>(channel);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
DysonNetwork.Shared/Etcd/IEtcdService.cs
Normal file
13
DysonNetwork.Shared/Etcd/IEtcdService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DysonNetwork.Shared.Etcd
|
||||
{
|
||||
public interface IEtcdService : IDisposable
|
||||
{
|
||||
Task RegisterServiceAsync(string serviceName, string serviceAddress, int ttl = 15);
|
||||
Task UnregisterServiceAsync();
|
||||
Task<List<string>> DiscoverServicesAsync(string serviceName);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user