Как обращались к зависшему сервису на проекте
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как обращались к зависшему сервису на проекте
Это один из самых частых и критичных сценариев в production окружении. Когда зависит сервис, от которого зависит наше приложение, нужно принять правильное решение в кратчайшие сроки.
Диагностика проблемы
1. Определение причины зависания
Первое, что нужно сделать - понять, что именно произошло:
- Сервис вообще не отвечает (timeout)
- Сервис отвечает очень медленно
- Сервис возвращает ошибки
- Сервис недоступен сетевым путём
Для проверки:
curl -v http://service.host:port/health
ping service.host
netstat -an | grep :port
2. Проверка сетевых соединений
Основная проблема - наше приложение может зависнуть, пытаясь подключиться к неотзывчивому сервису.
Решения на уровне кода
1. Timeout на HTTP клиенте
Всегда устанавливайте timeout при работе с внешними сервисами:
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 5 секунд
factory.setReadTimeout(10000); // 10 секунд
return new RestTemplate(factory);
}
Или с помощью OkHttp:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
2. Circuit Breaker паттерн (Resilience4j)
Это наиболее правильный способ - не пытаться подключиться к зависшему сервису снова и снова:
@Configuration
public class CircuitBreakerConfig {
@Bean
public CircuitBreaker circuitBreaker() {
CircuitBreakerConfig config = CircuitBreakerConfig
.custom()
.failureThreshold(50) // 50% ошибок
.slowCallRateThreshold(50) // 50% медленных запросов
.slowCallDurationThreshold(Duration.ofSeconds(5))
.waitDurationInOpenState(Duration.ofSeconds(30))
.build();
return CircuitBreaker.of("external-service", config);
}
}
@Service
public class ExternalServiceClient {
private final CircuitBreaker circuitBreaker;
private final RestTemplate restTemplate;
@CircuitBreaker(name = "external-service", fallbackMethod = "fallbackGetData")
public String getData(String id) {
return restTemplate.getForObject(
"http://external-service/api/data/" + id,
String.class
);
}
public String fallbackGetData(String id, Exception e) {
log.warn("Circuit breaker open, returning cached data for id: " + id);
return getCachedData(id);
}
}
3. Retry логика с exponential backoff
@Service
public class ResilientServiceClient {
@Retry(name = "external-service", fallbackMethod = "fallback")
@CircuitBreaker(name = "external-service")
public String callService(String id) {
return restTemplate.getForObject(
"http://external-service/api/data/" + id,
String.class
);
}
public String fallback(String id, Exception e) {
log.error("Failed to call service after retries: " + id, e);
return "FALLBACK_DATA";
}
}
// application.yml
resilience4j:
retry:
instances:
external-service:
maxAttempts: 3
waitDuration: 1000
intervalFunction: exponentialBackoff
exponentialBackoffMultiplier: 2
4. Thread Pool Isolation (Hystrix/Resilience4j)
Отдельный thread pool для каждого внешнего сервиса, чтобы потоки не блокировались:
@Configuration
public class ThreadPoolConfig {
@Bean
public Executor externalServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("external-service-");
executor.initialize();
return executor;
}
}
@Service
public class AsyncServiceClient {
@Async("externalServiceExecutor")
public CompletableFuture<String> callServiceAsync(String id) {
try {
String result = restTemplate.getForObject(
"http://external-service/api/data/" + id,
String.class
);
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}
Операционные меры
1. Мониторинг и алертинг
Настройте мониторинг на:
- Время ответа (latency) от сервиса
- Количество ошибок
- Количество активных соединений
- Потребление памяти и CPU
2. Graceful degradation
Не прерывайте пользовательский сценарий, если один сервис зависает:
@Service
public class ProductService {
private final PricingServiceClient pricingClient;
private final CacheManager cacheManager;
public Product getProduct(String id) {
Product product = getBaseProduct(id);
try {
BigDecimal price = pricingClient.getPrice(id);
product.setPrice(price);
} catch (ServiceUnavailableException e) {
// Зависает сервис цен - используем cached price
BigDecimal cachedPrice = getCachedPrice(id);
product.setPrice(cachedPrice);
log.warn("Using cached price for product: " + id);
}
return product;
}
}
3. Load Balancer настройки
upstream backend {
server service1.example.com:8080 max_fails=3 fail_timeout=30s;
server service2.example.com:8080 max_fails=3 fail_timeout=30s;
}
server {
location /api/ {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
proxy_send_timeout 5s;
}
}
4. Kubernetes liveness/readiness probes
apiVersion: v1
kind: Pod
metadata:
name: my-service
spec:
containers:
- name: app
image: myapp:latest
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Практический сценарий
Ситуация: Сервис аналитики перестаёт отвечать, из-за чего наше приложение начинает зависать.
Мой подход:
- Сразу же обнаруживаем благодаря мониторингу
- Circuit breaker отключает обращения к сервису (OPEN state)
- Приложение возвращает fallback ответ (cached данные или default значение)
- Пользователи продолжают работать, но без некоторых функций
- Команда диагностирует проблему в сервисе аналитики
- После исправления circuit breaker переходит в HALF_OPEN, пробует восстановить соединение
- Если успешно, переходит в CLOSED, всё работает нормально
Лучшие практики
- Всегда устанавливайте timeout при работе с сетевыми сервисами
- Используйте Circuit Breaker для защиты от cascading failures
- Реализуйте fallback стратегию (cache, default value, degraded mode)
- Мониторьте latency и error rates
- Используйте асинхронные вызовы с timeout'ами
- Настройте bulkhead паттерн (изоляция thread pool'ов)
- Имплементируйте graceful shutdown
- Тестируйте поведение приложения при недоступности зависимостей