Skip to content

Service Discovery (Servis Keşfi)

Genel Bakış

Service Discovery, mikroservis mimarisinde servislerin birbirlerini dinamik olarak bulabilmesini sağlayan bir mekanizmadır. Servislerin konumlarını ve durumlarını merkezi olarak yönetir.

Temel Özellikler

1. Servis Kaydı

// Consul Service Registration
public class ConsulServiceRegistration
{
    private readonly IConsulClient _consulClient;
    private readonly IConfiguration _configuration;

    public ConsulServiceRegistration(
        IConsulClient consulClient,
        IConfiguration configuration)
    {
        _consulClient = consulClient;
        _configuration = configuration;
    }

    public async Task RegisterServiceAsync()
    {
        var serviceId = $"{_configuration["Service:Name"]}-{Guid.NewGuid()}";
        var serviceRegistration = new AgentServiceRegistration
        {
            ID = serviceId,
            Name = _configuration["Service:Name"],
            Address = _configuration["Service:Host"],
            Port = int.Parse(_configuration["Service:Port"]),
            Check = new AgentServiceCheck
            {
                HTTP = $"http://{_configuration["Service:Host"]}:{_configuration["Service:Port"]}/health",
                Interval = TimeSpan.FromSeconds(10),
                Timeout = TimeSpan.FromSeconds(5),
                DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1)
            }
        };

        await _consulClient.Agent.ServiceRegister(serviceRegistration);
    }
}

// Health Check Endpoint
public class HealthCheckController : ControllerBase
{
    [HttpGet("health")]
    public IActionResult Check()
    {
        return Ok(new { status = "healthy" });
    }
}

2. Servis Keşfi

// Consul Service Discovery
public class ConsulServiceDiscovery
{
    private readonly IConsulClient _consulClient;

    public ConsulServiceDiscovery(IConsulClient consulClient)
    {
        _consulClient = consulClient;
    }

    public async Task<string> GetServiceUrlAsync(string serviceName)
    {
        var services = await _consulClient.Health.Service(serviceName);
        if (!services.Response.Any())
        {
            throw new ServiceNotFoundException(serviceName);
        }

        var service = services.Response.First();
        return $"http://{service.Service.Address}:{service.Service.Port}";
    }
}

// Service Discovery Middleware
public class ServiceDiscoveryMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IServiceDiscovery _serviceDiscovery;

    public ServiceDiscoveryMiddleware(
        RequestDelegate next,
        IServiceDiscovery serviceDiscovery)
    {
        _next = next;
        _serviceDiscovery = serviceDiscovery;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var serviceName = context.Request.Headers["X-Service-Name"];
        if (!string.IsNullOrEmpty(serviceName))
        {
            try
            {
                var serviceUrl = await _serviceDiscovery.GetServiceUrlAsync(serviceName);
                context.Request.Headers["X-Service-Url"] = serviceUrl;
            }
            catch (ServiceNotFoundException)
            {
                context.Response.StatusCode = 503;
                return;
            }
        }

        await _next(context);
    }
}

3. Health Checking

// Health Check Service
public class HealthCheckService : IHealthCheckService
{
    private readonly IEnumerable<IHealthCheck> _healthChecks;

    public HealthCheckService(IEnumerable<IHealthCheck> healthChecks)
    {
        _healthChecks = healthChecks;
    }

    public async Task<HealthCheckResult> CheckHealthAsync()
    {
        var results = new List<HealthCheckResult>();

        foreach (var healthCheck in _healthChecks)
        {
            try
            {
                var result = await healthCheck.CheckHealthAsync();
                results.Add(result);
            }
            catch (Exception ex)
            {
                results.Add(new HealthCheckResult(
                    healthCheck.Name,
                    HealthStatus.Unhealthy,
                    ex.Message));
            }
        }

        var status = results.All(r => r.Status == HealthStatus.Healthy)
            ? HealthStatus.Healthy
            : HealthStatus.Unhealthy;

        return new HealthCheckResult(status, results);
    }
}

// Database Health Check
public class DatabaseHealthCheck : IHealthCheck
{
    private readonly IDbConnection _dbConnection;

    public DatabaseHealthCheck(IDbConnection dbConnection)
    {
        _dbConnection = dbConnection;
    }

    public async Task<HealthCheckResult> CheckHealthAsync()
    {
        try
        {
            await _dbConnection.OpenAsync();
            await _dbConnection.CloseAsync();
            return new HealthCheckResult("Database", HealthStatus.Healthy);
        }
        catch (Exception ex)
        {
            return new HealthCheckResult(
                "Database",
                HealthStatus.Unhealthy,
                ex.Message);
        }
    }
}

4. Load Balancing

// Load Balancer
public class RoundRobinLoadBalancer : ILoadBalancer
{
    private readonly IServiceDiscovery _serviceDiscovery;
    private readonly ConcurrentDictionary<string, int> _serviceIndices = new();

    public RoundRobinLoadBalancer(IServiceDiscovery serviceDiscovery)
    {
        _serviceDiscovery = serviceDiscovery;
    }

    public async Task<string> GetServiceUrlAsync(string serviceName)
    {
        var services = await _serviceDiscovery.GetServicesAsync(serviceName);
        if (!services.Any())
        {
            throw new ServiceNotFoundException(serviceName);
        }

        var index = _serviceIndices.AddOrUpdate(
            serviceName,
            0,
            (_, current) => (current + 1) % services.Count);

        return services[index];
    }
}

// Load Balancing Middleware
public class LoadBalancingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILoadBalancer _loadBalancer;

    public LoadBalancingMiddleware(
        RequestDelegate next,
        ILoadBalancer loadBalancer)
    {
        _next = next;
        _loadBalancer = loadBalancer;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var serviceName = context.Request.Headers["X-Service-Name"];
        if (!string.IsNullOrEmpty(serviceName))
        {
            try
            {
                var serviceUrl = await _loadBalancer.GetServiceUrlAsync(serviceName);
                context.Request.Headers["X-Service-Url"] = serviceUrl;
            }
            catch (ServiceNotFoundException)
            {
                context.Response.StatusCode = 503;
                return;
            }
        }

        await _next(context);
    }
}

Best Practices

1. Servis Kaydı

  • Otomatik kayıt yapın
  • Health check ekleyin
  • Deregistration yapın
  • Metadata ekleyin

2. Servis Keşfi

  • Caching kullanın
  • Fallback mekanizması ekleyin
  • Timeout değerlerini ayarlayın
  • Retry mekanizması ekleyin

3. Health Checking

  • Düzenli kontrol yapın
  • Kritik servisleri izleyin
  • Detaylı raporlama yapın
  • Alerting ekleyin

4. Load Balancing

  • Farklı stratejiler kullanın
  • Session affinity uygulayın
  • Circuit breaker ekleyin
  • Monitoring yapın

Sık Sorulan Sorular

1. Service Discovery neden önemlidir?

  • Dinamik servis bulmayı sağlar
  • Yük dengelemeyi kolaylaştırır
  • Servis izolasyonunu sağlar
  • Scaling'i kolaylaştırır

2. Hangi Service Discovery çözümleri kullanılabilir?

  • Consul
  • Eureka
  • etcd
  • ZooKeeper
  • Kubernetes Service Discovery

3. Service Discovery'de hangi güvenlik önlemleri alınmalıdır?

  • SSL/TLS kullanılmalı
  • Authentication uygulanmalı
  • Authorization yapılmalı
  • Rate limiting uygulanmalı

Kaynaklar