Как система поймет, что микросервис Instance отключился
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Обнаружение отключённого микросервиса
В микросервисной архитектуре критично знать когда сервис недоступен. Существует несколько механизмов для этого.
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
-
Всегда реализуй health check endpoint
- /actuator/health (liveness)
- /actuator/health/readiness (readiness)
-
Используй правильные timeouts
- Connection timeout: 3-5 сек
- Read timeout: 5-10 сек
- Health check interval: 5-10 сек
-
Имплементируй graceful shutdown
- Вернуть NOT_READY на /health/readiness
- Завершить текущие запросы
- Закрыть соединения
-
Мониторь всё
- Prometheus metrics
- Alertmanager для alerts
- Centralized logging (ELK, Loki)
-
Используй правильный 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).