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

Какие знаешь способы оптимизации сервиса генерации отчётов, который делает 4 независимых HTTP-запроса к разным системам, кроме использования Thread?

3.0 Senior🔥 171 комментариев
#Многопоточность

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

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

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

Способы оптимизации сервиса генерации отчётов (без Thread)

Если четыре независимых HTTP-запроса выполняются последовательно, это создаёт узкое место. Вот несколько способов оптимизации без явного использования потоков.

1. CompletableFuture и асинхронные операции

Это лучший выбор для Java 8+:

import java.util.concurrent.CompletableFuture;
import org.springframework.web.client.RestTemplate;

public class ReportGenerationService {
    private RestTemplate restTemplate;
    
    // Оригинальный медленный способ (последовательно)
    public ReportData generateReportSync() {
        Data data1 = fetchFromSystem1();  // Ждём
        Data data2 = fetchFromSystem2();  // Ждём
        Data data3 = fetchFromSystem3();  // Ждём
        Data data4 = fetchFromSystem4();  // Ждём
        return combineData(data1, data2, data3, data4);
    }
    
    // Оптимизированный способ с CompletableFuture
    public CompletableFuture<ReportData> generateReportAsync() {
        CompletableFuture<Data> future1 = CompletableFuture.supplyAsync(
            this::fetchFromSystem1
        );
        CompletableFuture<Data> future2 = CompletableFuture.supplyAsync(
            this::fetchFromSystem2
        );
        CompletableFuture<Data> future3 = CompletableFuture.supplyAsync(
            this::fetchFromSystem3
        );
        CompletableFuture<Data> future4 = CompletableFuture.supplyAsync(
            this::fetchFromSystem4
        );
        
        // Ждём ВСЕ 4 запроса одновременно
        return CompletableFuture.allOf(future1, future2, future3, future4)
            .thenApply(v -> combineData(
                future1.join(),
                future2.join(),
                future3.join(),
                future4.join()
            ));
    }
    
    // Использование
    public ReportData getReport() throws Exception {
        return generateReportAsync().get(10, TimeUnit.SECONDS);
    }
    
    private Data fetchFromSystem1() {
        return restTemplate.getForObject("http://system1/api/data", Data.class);
    }
    
    private Data fetchFromSystem2() {
        return restTemplate.getForObject("http://system2/api/data", Data.class);
    }
    
    // ... fetchFromSystem3 и 4
    
    private ReportData combineData(Data d1, Data d2, Data d3, Data d4) {
        return new ReportData(d1, d2, d3, d4);
    }
}

Преимущества:

  • Параллельное выполнение 4 запросов
  • Время = макс(time1, time2, time3, time4), а не сумма
  • Встроено в Java, не требует зависимостей

2. Project Reactor и Reactive Streams

Для более сложных сценариев используйте Reactor:

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.web.reactive.function.client.WebClient;

public class ReactiveReportService {
    private WebClient webClient;
    
    public Mono<ReportData> generateReportReactive() {
        Mono<Data> data1 = webClient.get()
            .uri("http://system1/api/data")
            .retrieve()
            .bodyToMono(Data.class);
            
        Mono<Data> data2 = webClient.get()
            .uri("http://system2/api/data")
            .retrieve()
            .bodyToMono(Data.class);
            
        Mono<Data> data3 = webClient.get()
            .uri("http://system3/api/data")
            .retrieve()
            .bodyToMono(Data.class);
            
        Mono<Data> data4 = webClient.get()
            .uri("http://system4/api/data")
            .retrieve()
            .bodyToMono(Data.class);
        
        // Объединяем все 4 запроса
        return Mono.zip(data1, data2, data3, data4)
            .map(tuple -> combineData(
                tuple.getT1(),
                tuple.getT2(),
                tuple.getT3(),
                tuple.getT4()
            ))
            .doOnError(error -> logger.error("Error generating report", error))
            .timeout(Duration.ofSeconds(10))
            .retry(2);  // Автоматический retry
    }
    
    private ReportData combineData(Data d1, Data d2, Data d3, Data d4) {
        return new ReportData(d1, d2, d3, d4);
    }
}

// Использование в Controller
@GetMapping("/report")
public Mono<ReportData> getReport() {
    return reportService.generateReportReactive();
}

Преимущества Reactor:

  • Backpressure handling
  • Lazy evaluation
  • Встроенный error handling и retry
  • Оптимален для Spring WebFlux

3. Кэширование (Redis)

Если данные не меняются часто:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;

@Service
public class CachedReportService {
    
    @Cacheable(value = "report", key = "#reportId", unless = "#result == null")
    public ReportData getReport(String reportId) {
        return generateReportAsync().join();
    }
    
    @CacheEvict(value = "report", allEntries = true)
    @Scheduled(fixedDelay = 300000)  // Очищаем каждые 5 минут
    public void clearReportCache() {
        logger.info("Report cache cleared");
    }
    
    private CompletableFuture<ReportData> generateReportAsync() {
        // ... код выше
    }
}

Конфигурация (application.yml):

spring:
  cache:
    type: redis
    redis:
      time-to-live: 300000  # 5 минут

4. Request Batching и Aggregation

Если система поддерживает batch-запросы:

public class BatchedReportService {
    
    public ReportData generateReportWithBatch() {
        // Один запрос вместо четырёх
        BatchRequest request = new BatchRequest(
            "http://system1/api/data",
            "http://system2/api/data",
            "http://system3/api/data",
            "http://system4/api/data"
        );
        
        BatchResponse response = restTemplate.postForObject(
            "http://batch-gateway/batch",
            request,
            BatchResponse.class
        );
        
        return combineData(
            response.getData1(),
            response.getData2(),
            response.getData3(),
            response.getData4()
        );
    }
}

5. CDN и Edge Caching

Для статичных или редко меняющихся данных:

public class CDNOptimizedService {
    // Используем CDN вместо прямых запросов к системам
    // Кэш-время: 1 час
    private String fetchWithCDN(String systemUrl) {
        String cdnUrl = "https://cdn.company.com/cache/" + 
            Base64.encode(systemUrl);
        return restTemplate.getForObject(cdnUrl, String.class);
    }
}

6. Connection Pooling и HTTP Client оптимизация

import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        // Настройка connection pool
        PoolingHttpClientConnectionManager cm = 
            new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(100);           // Макс 100 соединений
        cm.setDefaultMaxPerRoute(20);  // Макс 20 на хост
        
        CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(cm)
            .build();
        
        HttpComponentsClientHttpRequestFactory factory = 
            new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(3000);   // 3 сек
        factory.setReadTimeout(5000);      // 5 сек
        
        return new RestTemplate(factory);
    }
}

7. Circuit Breaker (Resilience4j)

Для отказоустойчивости при слабых системах:

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;

@Service
public class ResilientReportService {
    
    @CircuitBreaker(name = "system1", fallbackMethod = "fallbackData")
    @Retry(name = "system1")
    @TimeLimiter(name = "system1")
    public CompletableFuture<Data> fetchFromSystem1() {
        return CompletableFuture.supplyAsync(() -> 
            restTemplate.getForObject("http://system1/api/data", Data.class)
        );
    }
    
    public CompletableFuture<Data> fallbackData(Exception ex) {
        logger.warn("Using fallback for system1", ex);
        return CompletableFuture.completedFuture(new Data("fallback"));
    }
}

// application.yml
# resilience4j:
#   circuitbreaker:
#     instances:
#       system1:
#         registerHealthIndicator: true
#         failureRateThreshold: 50
#         waitDurationInOpenState: 15000
#   retry:
#     instances:
#       system1:
#         maxAttempts: 3
#         waitDuration: 1000

8. Streaming и Chunked Processing

Для больших отчётов:

public class StreamingReportService {
    
    public void generateStreamingReport(OutputStream output) {
        DataOutputStream dos = new DataOutputStream(output);
        
        // Параллельно получаем и пишем
        CompletableFuture.allOf(
            CompletableFuture.supplyAsync(() -> fetchFromSystem1())
                .thenAccept(data -> writeToStream(dos, data)),
            CompletableFuture.supplyAsync(() -> fetchFromSystem2())
                .thenAccept(data -> writeToStream(dos, data)),
            CompletableFuture.supplyAsync(() -> fetchFromSystem3())
                .thenAccept(data -> writeToStream(dos, data)),
            CompletableFuture.supplyAsync(() -> fetchFromSystem4())
                .thenAccept(data -> writeToStream(dos, data))
        ).join();
    }
}

Сравнение подходов

МетодСложностьВремяКогда использовать
CompletableFutureНизкаяBestСтандартный выбор
ReactorСредняяBestSpring WebFlux
КэшНизкаяExcellentСтатичные данные
Batch APIСредняяExcellentПоддерживается
Circuit BreakerСредняяGoodНенадёжные системы
StreamingВысокаяGoodБольшие объёмы

Production вариант

@Service
public class OptimizedReportService {
    private RestTemplate restTemplate;
    private final ExecutorService executor = 
        Executors.newFixedThreadPool(10);
    
    @Cacheable(value = "reports", key = "#reportId", unless = "#result == null")
    public ReportData getReport(String reportId) {
        return CompletableFuture.supplyAsync(
            () -> fetchFromSystem1(), executor
        )
        .thenCombineAsync(
            CompletableFuture.supplyAsync(() -> fetchFromSystem2(), executor),
            this::mergeData1
        )
        .thenCombineAsync(
            CompletableFuture.supplyAsync(() -> fetchFromSystem3(), executor),
            this::mergeData2
        )
        .thenCombineAsync(
            CompletableFuture.supplyAsync(() -> fetchFromSystem4(), executor),
            this::mergeData3
        )
        .orTimeout(10, TimeUnit.SECONDS)
        .exceptionally(ex -> {
            logger.error("Report generation failed", ex);
            return getDefaultReport();
        })
        .join();
    }
}

Заключение

Для оптимизации отчётного сервиса без явных потоков:

  1. CompletableFuture — простое параллельное выполнение
  2. Reactor — если используешь Spring WebFlux
  3. Кэширование — если данные стабильны
  4. Batch запросы — если система поддерживает
  5. Circuit Breaker — для отказоустойчивости
  6. Connection pooling — оптимизация HTTP клиента

Обычно комбинируют несколько подходов: CompletableFuture для параллелизма + Redis кэш для стабильных данных + Circuit Breaker для надёжности.