← Назад к вопросам

Как система поймет, что микросервис Instance отключился

2.0 Middle🔥 121 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обнаружение отключённого микросервиса

В микросервисной архитектуре критично знать когда сервис недоступен. Существует несколько механизмов для этого.

1. Health Check Endpoints

Самый простой и надёжный способ — HTTP health check endpoint:

@RestController
@RequestMapping("/actuator")
public class HealthController {
    
    @Autowired private DataSource datasource;
    @Autowired private ExternalService externalService;
    
    // Spring Boot actuator автоматически создаёт /actuator/health
    @GetMapping("/health")
    public ResponseEntity<HealthStatus> health() {
        try {
            // Проверяю БД
            datasource.getConnection().close();
            
            // Проверяю зависимые сервисы
            externalService.ping();
            
            return ResponseEntity.ok(new HealthStatus("UP"));
        } catch (Exception e) {
            return ResponseEntity.status(503)
                .body(new HealthStatus("DOWN", e.getMessage()));
        }
    }
}

// Результат:
// GET /actuator/health
// {
//   "status": "UP",
//   "components": {
//     "db": { "status": "UP" },
//     "externalService": { "status": "UP" }
//   }
// }

Левел здоровья:

  • UP (200) — всё работает
  • DOWN (503) — проблемы
  • OUT_OF_SERVICE (503) — обслуживание

2. Load Balancer Health Checks

Load balancer (Nginx, HAProxy, AWS ELB) периодически проверяет сервисы:

# Nginx конфигурация
upstream backend {
    server 192.168.1.1:8080;
    server 192.168.1.2:8080;
    server 192.168.1.3:8080;
    
    # Health check каждые 5 секунд
    check interval=5000 rise=2 fall=5 timeout=1000 type=http;
    check_http_send "GET /actuator/health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

# Логика:
# 1. Отправляю GET /actuator/health каждые 5 сек
# 2. Если DOWN - удаляю из пула после 5 неудачных попыток
# 3. Если UP - добавляю обратно после 2 успешных проверок

3. Service Discovery: Consul/Eureka

Spring Cloud Eureka:

// Сервис регистрируется в Eureka
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

// application.yml
eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka-server:8761/eureka
  instance:
    leaseRenewalIntervalInSeconds: 10  # Heartbeat каждые 10 сек
    leaseExpirationDurationInSeconds: 30  # Удалить если нет heartbeat 30 сек

// Eureka Server отслеживает:
Eureka Server (Service Registry)
├─ Service: user-service
│  ├─ Instance 1: 192.168.1.1:8080 (UP)
│  ├─ Instance 2: 192.168.1.2:8080 (UP)
│  └─ Instance 3: 192.168.1.3:8080 (DOWN - нет heartbeat 35 сек)
└─ Service: order-service
   ├─ Instance 1: 192.168.1.10:8080 (UP)
   └─ Instance 2: 192.168.1.11:8080 (DOWN)

Как работает Eureka:

// Клиент регистрируется
// 1. POST /eureka/apps/USER-SERVICE
// Instance: id=i-123, status=UP, leaseExpiration=now+30s

// Каждые 10 сек отправляет heartbeat
// 2. PUT /eureka/apps/USER-SERVICE/i-123
// renew lastHeartbeatTime

// Если heartbeat не приходит 30 сек
// 3. Eureka Server удаляет instance из реестра
// Status -> DOWN

// Другие сервисы узнают что instance DOWN
// LoadBalancer/Ribbon перестаёт отправлять туда запросы

4. Kubernetes Liveness & Readiness Probes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: user-service
        image: myrepo/user-service:1.0
        ports:
        - containerPort: 8080
        
        # Liveness Probe: Живой ли контейнер?
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 30  # Жди 30 сек перед первой проверкой
          periodSeconds: 10        # Проверяй каждые 10 сек
          timeoutSeconds: 5        # Timeout 5 сек
          failureThreshold: 3      # Перезапусти после 3 неудач
        
        # Readiness Probe: Готов ли принимать трафик?
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          failureThreshold: 3  # Удали из service после 3 неудач

# Что происходит:
# 1. Pod стартует
# 2. Liveness OK -> контейнер живой
# 3. Readiness OK -> сервис добавляет pod в load balancer
# 4. Если readiness FAIL -> удалить из LB, но контейнер не перезапускается
# 5. Если liveness FAIL -> перезапустить контейнер

5. Heartbeat Mechanism

// Сервис отправляет heartbeat в центр мониторинга
@Component
public class HeartbeatService {
    
    @Autowired private RestTemplate restTemplate;
    
    @Scheduled(fixedRate = 10000)  // Каждые 10 сек
    public void sendHeartbeat() {
        try {
            HeartbeatMessage msg = new HeartbeatMessage(
                instanceId = "service-1",
                timestamp = System.currentTimeMillis(),
                status = "UP",
                memory = Runtime.getRuntime().totalMemory()
            );
            
            restTemplate.postForObject(
                "http://heartbeat-server:9000/heartbeat",
                msg,
                Void.class
            );
        } catch (Exception e) {
            logger.error("Heartbeat failed", e);
        }
    }
}

// Heartbeat Server отслеживает
Heartbeat Server
├─ Instance: service-1 (last heartbeat 2 sec ago) -> UP
├─ Instance: service-2 (last heartbeat 45 sec ago) -> DOWN!
├─ Instance: service-3 (last heartbeat 8 sec ago) -> UP

// Если heartbeat не приходит 30 сек -> Service is DOWN

6. Monitoring Stack: Prometheus + Alertmanager

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'user-service'
    static_configs:
      - targets:
          - 'user-service-1:8080'
          - 'user-service-2:8080'
          - 'user-service-3:8080'
    metrics_path: '/actuator/prometheus'

# Prometheus периодически scrape'ит метрики
# GET /actuator/prometheus
# # HELP up Whether the instance is up
# # TYPE up gauge
# up{job="user-service",instance="user-service-1:8080"} 1  (UP)
# up{job="user-service",instance="user-service-2:8080"} 0  (DOWN!)
# rules.yml - Alert Rules
groups:
  - name: service_health
    rules:
      - alert: ServiceDown
        expr: up{job="user-service"} == 0
        for: 1m  # Alert если DOWN больше 1 минуты
        annotations:
          summary: "User Service Instance Down"
          description: "{{ $labels.instance }} has been down for 1 minute"

# Алертинг:
# 1. Prometheus видит up == 0
# 2. Ждёт 1 минуту
# 3. Отправляет alert в Alertmanager
# 4. Alertmanager отправляет в Slack/PagerDuty/Email

7. Network-level Detection: TCP timeout

// Когда система пытается подключиться
Socket socket = new Socket();
try {
    socket.connect(new InetSocketAddress("service:8080"), 5000);  // 5 sec timeout
    socket.close();
    // Успешно подключился - сервис UP
} catch (SocketTimeoutException e) {
    // Timeout - сервис DOWN
    logger.warn("Service timeout");
} catch (ConnectException e) {
    // Connection refused - сервис DOWN
    logger.warn("Service refused connection");
}

8. Graceful Shutdown Detection

// Когда сервис получает SIGTERM
@Component
public class GracefulShutdown {
    
    private volatile boolean shuttingDown = false;
    
    @PreDestroy
    public void shutdown() {
        shuttingDown = true;
        logger.info("Starting graceful shutdown...");
        
        // Health check должна вернуть NOT_READY
        // Load balancer перестанет отправлять новые запросы
    }
    
    @GetMapping("/actuator/health/readiness")
    public ResponseEntity<?> readiness() {
        if (shuttingDown) {
            return ResponseEntity.status(503).body("Shutting down");
        }
        return ResponseEntity.ok("Ready");
    }
}

// Цепочка событий:
// 1. SIGTERM сигнал -> @PreDestroy
// 2. shuttingDown = true
// 3. Health check вернёт 503 (NOT_READY)
// 4. Load balancer удалит из пула
// 5. Текущие запросы завершаются
// 6. Сервис выключается

Best Practices

  1. Всегда реализуй health check endpoint

    • /actuator/health (liveness)
    • /actuator/health/readiness (readiness)
  2. Используй правильные timeouts

    • Connection timeout: 3-5 сек
    • Read timeout: 5-10 сек
    • Health check interval: 5-10 сек
  3. Имплементируй graceful shutdown

    • Вернуть NOT_READY на /health/readiness
    • Завершить текущие запросы
    • Закрыть соединения
  4. Мониторь всё

    • Prometheus metrics
    • Alertmanager для alerts
    • Centralized logging (ELK, Loki)
  5. Используй правильный orchest orchestrator

    • Kubernetes: liveness + readiness probes
    • Docker Swarm: health checks
    • Manual: Eureka + Ribbon

Заключение

Обнаружение отключённого микросервиса — это комбинация: health checks, heartbeats, service discovery, и monitoring. В modern архитектуре это часто handled Kubernetes или Service Mesh (Istio). Ключ — быстрое обнаружение (5-10 сек) и автоматическое действие (удалить из пула, перезапустить, alert).

Как система поймет, что микросервис Instance отключился | PrepBro