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

Как обращались к зависшему сервису на проекте

1.8 Middle🔥 161 комментариев
#ООП#Основы Java

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

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

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

# Как обращались к зависшему сервису на проекте

Это один из самых частых и критичных сценариев в 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

Практический сценарий

Ситуация: Сервис аналитики перестаёт отвечать, из-за чего наше приложение начинает зависать.

Мой подход:

  1. Сразу же обнаруживаем благодаря мониторингу
  2. Circuit breaker отключает обращения к сервису (OPEN state)
  3. Приложение возвращает fallback ответ (cached данные или default значение)
  4. Пользователи продолжают работать, но без некоторых функций
  5. Команда диагностирует проблему в сервисе аналитики
  6. После исправления circuit breaker переходит в HALF_OPEN, пробует восстановить соединение
  7. Если успешно, переходит в CLOSED, всё работает нормально

Лучшие практики

  • Всегда устанавливайте timeout при работе с сетевыми сервисами
  • Используйте Circuit Breaker для защиты от cascading failures
  • Реализуйте fallback стратегию (cache, default value, degraded mode)
  • Мониторьте latency и error rates
  • Используйте асинхронные вызовы с timeout'ами
  • Настройте bulkhead паттерн (изоляция thread pool'ов)
  • Имплементируйте graceful shutdown
  • Тестируйте поведение приложения при недоступности зависимостей