← Назад к вопросам
Какие знаешь способы оптимизации сервиса генерации отчётов, который делает 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 | Средняя | Best | Spring 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();
}
}
Заключение
Для оптимизации отчётного сервиса без явных потоков:
- CompletableFuture — простое параллельное выполнение
- Reactor — если используешь Spring WebFlux
- Кэширование — если данные стабильны
- Batch запросы — если система поддерживает
- Circuit Breaker — для отказоустойчивости
- Connection pooling — оптимизация HTTP клиента
Обычно комбинируют несколько подходов: CompletableFuture для параллелизма + Redis кэш для стабильных данных + Circuit Breaker для надёжности.