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

Как посчитать среднее время ответа

2.0 Middle🔥 191 комментариев
#REST API и микросервисы

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

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

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

# Как посчитать среднее время ответа

Основной подход в Java приложениях

Вычисление среднего времени ответа (average response time) — это классическая задача в мониторинге и обработке запросов. В Java существует несколько подходов, каждый имеет свои преимущества в зависимости от контекста.

Решение 1: Простой подсчёт с System.currentTimeMillis()

Самый базовый способ для небольших нагрузок.

import java.util.ArrayList;
import java.util.List;

public class SimpleResponseTimeCalculator {
    
    private List<Long> responseTimes = new ArrayList<>();
    
    // Запускаем операцию и измеряем время
    public void measureOperation() {
        long startTime = System.currentTimeMillis();
        
        try {
            // Выполнить запрос/операцию
            performRequest();
        } finally {
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;  // в миллисекундах
            responseTimes.add(responseTime);
        }
    }
    
    // Вычислить среднее время
    public double getAverageResponseTime() {
        if (responseTimes.isEmpty()) {
            return 0;
        }
        
        long sum = 0;
        for (long time : responseTimes) {
            sum += time;
        }
        
        return sum / (double) responseTimes.size();
    }
    
    private void performRequest() {
        // Симуляция запроса
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// Использование
SimpleResponseTimeCalculator calc = new SimpleResponseTimeCalculator();
for (int i = 0; i < 1000; i++) {
    calc.measureOperation();
}
System.out.println("Average: " + calc.getAverageResponseTime() + " ms");

Решение 2: Более точное измерение с System.nanoTime()

Для более точных измерений (микросекунды) используй nanoTime().

public class NanoTimeResponseCalculator {
    
    private List<Long> responseTimes = new ArrayList<>();
    
    public void measureOperation() {
        long startTime = System.nanoTime();
        
        try {
            performRequest();
        } finally {
            long endTime = System.nanoTime();
            long responseTimeNanos = endTime - startTime;
            responseTimes.add(responseTimeNanos);
        }
    }
    
    public double getAverageResponseTimeMs() {
        if (responseTimes.isEmpty()) {
            return 0;
        }
        
        long sum = 0;
        for (long time : responseTimes) {
            sum += time;
        }
        
        // Конвертируем из наносекунд в миллисекунды
        return sum / (responseTimes.size() * 1_000_000.0);
    }
    
    public long getAverageResponseTimeNanos() {
        if (responseTimes.isEmpty()) {
            return 0;
        }
        
        long sum = 0;
        for (long time : responseTimes) {
            sum += time;
        }
        
        return sum / responseTimes.size();
    }
}

Решение 3: С использованием Spring AOP (Aspect-Oriented Programming)

Для веб-приложений измеряй время автоматически через AOP.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Aspect
@Component
public class ResponseTimeAspect {
    
    private final ConcurrentHashMap<String, ResponseTimeStats> stats = new ConcurrentHashMap<>();
    
    // Перехватываем все методы контроллера
    @Around("execution(* com.example.controller..*(..))")
    public Object measureResponseTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        long startTime = System.nanoTime();
        
        try {
            return joinPoint.proceed();
        } finally {
            long endTime = System.nanoTime();
            long responseTimeMs = (endTime - startTime) / 1_000_000;
            
            // Обновляем статистику
            stats.computeIfAbsent(methodName, k -> new ResponseTimeStats())
                 .addResponseTime(responseTimeMs);
            
            System.out.println(methodName + " took " + responseTimeMs + " ms");
        }
    }
    
    public ResponseTimeStats getStats(String methodName) {
        return stats.get(methodName);
    }
    
    // Класс для хранения статистики
    public static class ResponseTimeStats {
        private AtomicLong count = new AtomicLong(0);
        private AtomicLong totalTime = new AtomicLong(0);
        private volatile long minTime = Long.MAX_VALUE;
        private volatile long maxTime = Long.MIN_VALUE;
        
        public void addResponseTime(long timeMs) {
            count.incrementAndGet();
            totalTime.addAndGet(timeMs);
            
            synchronized (this) {
                minTime = Math.min(minTime, timeMs);
                maxTime = Math.max(maxTime, timeMs);
            }
        }
        
        public double getAverageTime() {
            if (count.get() == 0) return 0;
            return totalTime.get() / (double) count.get();
        }
        
        public long getMinTime() {
            return minTime == Long.MAX_VALUE ? 0 : minTime;
        }
        
        public long getMaxTime() {
            return maxTime == Long.MIN_VALUE ? 0 : maxTime;
        }
        
        public long getCount() {
            return count.get();
        }
    }
}

// Использование в контроллере
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable UUID id) {
        // Время измеряется автоматически через AOP
        return ResponseEntity.ok(userService.getUser(id));
    }
}

Решение 4: HTTP запросы через HttpClient

Для измерения времени ответа внешних сервисов.

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

public class HttpResponseTimeCalculator {
    
    private HttpClient httpClient = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(5))
        .build();
    
    private List<Long> responseTimes = new ArrayList<>();
    
    // Выполнить HTTP запрос и измерить время
    public String makeRequest(String url) {
        long startTime = System.currentTimeMillis();
        
        try {
            HttpRequest request = HttpRequest.newBuilder()
                .uri(java.net.URI.create(url))
                .timeout(Duration.ofSeconds(10))
                .GET()
                .build();
            
            HttpResponse<String> response = httpClient.send(
                request,
                HttpResponse.BodyHandlers.ofString()
            );
            
            return response.body();
            
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            responseTimes.add(responseTime);
        }
    }
    
    public double getAverageResponseTime() {
        if (responseTimes.isEmpty()) return 0;
        return responseTimes.stream()
            .mapToLong(Long::longValue)
            .average()
            .orElse(0);
    }
    
    public long getMedianResponseTime() {
        if (responseTimes.isEmpty()) return 0;
        List<Long> sorted = new ArrayList<>(responseTimes);
        sorted.sort(null);
        return sorted.get(sorted.size() / 2);
    }
    
    public long getPercentile95() {
        if (responseTimes.isEmpty()) return 0;
        List<Long> sorted = new ArrayList<>(responseTimes);
        sorted.sort(null);
        int index = (int) (sorted.size() * 0.95);
        return sorted.get(Math.min(index, sorted.size() - 1));
    }
}

// Использование
HttpResponseTimeCalculator calc = new HttpResponseTimeCalculator();
for (int i = 0; i < 100; i++) {
    calc.makeRequest("https://api.example.com/users");
}

System.out.println("Average: " + calc.getAverageResponseTime() + " ms");
System.out.println("Median: " + calc.getMedianResponseTime() + " ms");
System.out.println("P95: " + calc.getPercentile95() + " ms");

Решение 5: Micrometer (современный стандарт)

Модерный способ в Spring Boot приложениях.

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Service;

@Service
public class UserServiceWithMetrics {
    
    private final MeterRegistry meterRegistry;
    
    public UserServiceWithMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    public User getUser(UUID id) {
        // Использо вать Timer из Micrometer
        Timer timer = Timer.builder("user.get.time")
            .description("Time taken to fetch user")
            .register(meterRegistry);
        
        return timer.recordCallable(() -> fetchUserFromDatabase(id));
    }
    
    // Альтернатива: использовать @Timed из Spring
    @Timed(value = "user.list.time", description = "Time taken to list users")
    public List<User> listUsers() {
        return fetchUsersFromDatabase();
    }
    
    private User fetchUserFromDatabase(UUID id) {
        // БД запрос
        return new User();
    }
    
    private List<User> fetchUsersFromDatabase() {
        return new ArrayList<>();
    }
}

// application.yml
# management:
#   endpoints:
#     web:
#       exposure:
#         include: metrics,prometheus
#   metrics:
#     export:
#       prometheus:
#         enabled: true

Решение 6: Запись в базу данных для анализа

Для долгосрочного анализа храни метрики в БД.

@Entity
@Table(name = "response_times")
public class ResponseTimeRecord {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    @Column(name = "endpoint")
    private String endpoint;
    
    @Column(name = "response_time_ms")
    private Long responseTimeMs;
    
    @Column(name = "timestamp")
    private LocalDateTime timestamp;
    
    @Column(name = "status_code")
    private Integer statusCode;
}

@Repository
public interface ResponseTimeRepository extends JpaRepository<ResponseTimeRecord, UUID> {
    
    @Query("SELECT AVG(r.responseTimeMs) FROM ResponseTimeRecord r " +
           "WHERE r.endpoint = :endpoint AND r.timestamp >= :from")
    Double getAverageResponseTime(
        @Param("endpoint") String endpoint,
        @Param("from") LocalDateTime from
    );
    
    @Query("SELECT r FROM ResponseTimeRecord r " +
           "WHERE r.endpoint = :endpoint " +
           "ORDER BY r.responseTimeMs DESC LIMIT 1")
    Optional<ResponseTimeRecord> getMaxResponseTime(@Param("endpoint") String endpoint);
}

// Service для сохранения метрик
@Service
public class MetricsService {
    
    @Autowired
    private ResponseTimeRepository responseTimeRepository;
    
    public void recordResponseTime(String endpoint, long responseTimeMs, int statusCode) {
        ResponseTimeRecord record = new ResponseTimeRecord();
        record.setEndpoint(endpoint);
        record.setResponseTimeMs(responseTimeMs);
        record.setStatusCode(statusCode);
        record.setTimestamp(LocalDateTime.now());
        responseTimeRepository.save(record);
    }
    
    public double getAverageResponseTimeLastHour(String endpoint) {
        LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
        Double avg = responseTimeRepository.getAverageResponseTime(endpoint, oneHourAgo);
        return avg != null ? avg : 0;
    }
}

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

ПодходТочностьСложностьКогда использовать
currentTimeMillis~1msНизкаяПростые случаи
nanoTime~100nsНизкаяТочные измерения
Spring AOPХорошаяСредняяWebApps с контроллерами
HttpClientХорошаяСредняяВнешние сервисы
MicrometerОтличнаяСредняяProduction Spring Boot
БД запросыОтличнаяВысокаяДолгосрочный анализ

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

  1. Используй nanoTime() для микроскопических измерений (~100ns точность)
  2. currentTimeMillis() достаточна для HTTP запросов (~1ms)
  3. Используй Micrometer в Spring Boot — industry standard
  4. Собирай перцентили (p50, p95, p99), не только среднее
  5. Логируй и отслеживай тренды для раннего обнаружения проблем

Вывод: выбери подход в зависимости от контекста. Для веб-приложений используй Micrometer с хранением в Prometheus/Grafana. Для простых операций — System.nanoTime(). Для долгосрочного анализа — храни метрики в БД.