Автор оригинала: Eugen Paraschiv.
1. Обзор
В этом уроке мы интегрируем основные метрики в API Spring REST .
Мы создадим функциональность метрики сначала с помощью простых фильтров сервлетов, а затем с помощью пружинного загрузочного привода.
2. web.xml
Давайте начнем с регистрации фильтра – ” MetricFilter ” – в web.xml нашего приложения:
metricFilter org.baeldung.web.metric.MetricFilter metricFilter /*
Обратите внимание, как мы сопоставляем фильтр, чтобы охватить все поступающие запросы– “/*” – что, конечно, полностью настраивается.
3. Фильтр сервлетов
Теперь – давайте создадим наш пользовательский фильтр:
public class MetricFilter implements Filter { private MetricService metricService; @Override public void init(FilterConfig config) throws ServletException { metricService = (MetricService) WebApplicationContextUtils .getRequiredWebApplicationContext(config.getServletContext()) .getBean("metricService"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException { HttpServletRequest httpRequest = ((HttpServletRequest) request); String req = httpRequest.getMethod() + " " + httpRequest.getRequestURI(); chain.doFilter(request, response); int status = ((HttpServletResponse) response).getStatus(); metricService.increaseCount(req, status); } }
Поскольку фильтр не является стандартным компонентом, мы не собираемся вводить metricService , а вместо этого извлекаем его вручную – через ServletContext .
Также обратите внимание, что мы продолжаем выполнение цепочки фильтров, вызывая API doFilter здесь.
4. Метрика – Количество Кодов Состояния
Далее – давайте взглянем на наш простой MetricService :
@Service public class MetricService { private ConcurrentMapstatusMetric; public MetricService() { statusMetric = new ConcurrentHashMap (); } public void increaseCount(String request, int status) { Integer statusCount = statusMetric.get(status); if (statusCount == null) { statusMetric.put(status, 1); } else { statusMetric.put(status, statusCount + 1); } } public Map getStatusMetric() { return statusMetric; } }
Мы используем в памяти ConcurrentMap для хранения подсчетов для каждого типа кода состояния HTTP.
Теперь, чтобы отобразить эту базовую метрику, мы сопоставим ее с методом Controller :
@RequestMapping(value = "/status-metric", method = RequestMethod.GET) @ResponseBody public Map getStatusMetric() { return metricService.getStatusMetric(); }
А вот пример ответа:
{ "404":1, "200":6, "409":1 }
5. Метрика – Коды состояния по запросу
Далее – давайте запишем метрики для подсчетов по запросу :
@Service public class MetricService { private ConcurrentMap> metricMap; public void increaseCount(String request, int status) { ConcurrentHashMap statusMap = metricMap.get(request); if (statusMap == null) { statusMap = new ConcurrentHashMap (); } Integer count = statusMap.get(status); if (count == null) { count = 1; } else { count++; } statusMap.put(status, count); metricMap.put(request, statusMap); } public Map getFullMetric() { return metricMap; } }
Мы будем отображать результаты метрики через API:
@RequestMapping(value = "/metric", method = RequestMethod.GET) @ResponseBody public Map getMetric() { return metricService.getFullMetric(); }
Вот как выглядят эти показатели:
{ "GET /users": { "200":6, "409":1 }, "GET /users/1": { "404":1 } }
В соответствии с приведенным выше примером API выполнял следующие действия:
- “7” запросов на “GET /users “
- “6” из них привели к “200” ответам на код состояния и только один из “409”
6. Данные Метрических Временных рядов
Общие показатели несколько полезны в приложении, но если система работает в течение значительного количества времени – трудно сказать, что на самом деле означают эти показатели .
Вам нужен контекст времени, чтобы данные имели смысл и были легко интерпретированы.
Давайте теперь построим простую метрику, основанную на времени; мы будем вести учет количества кодов состояния в минуту-следующим образом:
@Service public class MetricService{ private ConcurrentMap> timeMap; private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); public void increaseCount(String request, int status) { String time = dateFormat.format(new Date()); ConcurrentHashMap statusMap = timeMap.get(time); if (statusMap == null) { statusMap = new ConcurrentHashMap (); } Integer count = statusMap.get(status); if (count == null) { count = 1; } else { count++; } statusMap.put(status, count); timeMap.put(time, statusMap); } }
И getGraphData() :
public Object[][] getGraphData() { int colCount = statusMetric.keySet().size() + 1; SetallStatus = statusMetric.keySet(); int rowCount = timeMap.keySet().size() + 1; Object[][] result = new Object[rowCount][colCount]; result[0][0] = "Time"; int j = 1; for (int status : allStatus) { result[0][j] = status; j++; } int i = 1; ConcurrentMap tempMap; for (Entry > entry : timeMap.entrySet()) { result[i][0] = entry.getKey(); tempMap = entry.getValue(); for (j = 1; j < colCount; j++) { result[i][j] = tempMap.get(result[0][j]); if (result[i][j] == null) { result[i][j] = 0; } } i++; } return result; }
Теперь мы собираемся сопоставить это с API:
@RequestMapping(value = "/metric-graph-data", method = RequestMethod.GET) @ResponseBody public Object[][] getMetricData() { return metricService.getGraphData(); }
И, наконец, мы собираемся визуализировать его с помощью Google Charts:
Metric Graph
7. Использование Пружинного ботинка 1.x Привод
В следующих нескольких разделах мы рассмотрим функциональность привода в Spring Boot, чтобы представить наши показатели.
Во – первых, нам нужно будет добавить зависимость привода в наш pom.xml :
org.springframework.boot spring-boot-starter-actuator
7.1. Метрический фильтр
Далее – мы можем превратить MetricFilter – в настоящий весенний боб:
@Component public class MetricFilter implements Filter { @Autowired private MetricService metricService; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException { chain.doFilter(request, response); int status = ((HttpServletResponse) response).getStatus(); metricService.increaseCount(status); } }
Это, конечно, незначительное упрощение, но его стоит сделать, чтобы избавиться от ранее ручной проводки зависимостей.
7.2. Использование Встречного Сервиса
Теперь давайте использовать службу Counter для подсчета вхождений для каждого кода состояния:
@Service public class MetricService { @Autowired private CounterService counter; private ListstatusList; public void increaseCount(int status) { counter.increment("status." + status); if (!statusList.contains("counter.status." + status)) { statusList.add("counter.status." + status); } } }
7.3. Экспорт Метрик С Помощью Репозитория Метрик
Далее – нам нужно экспортировать метрики – используя MetricRepository :
@Service public class MetricService { @Autowired private MetricRepository repo; private List> statusMetric; private List statusList; @Scheduled(fixedDelay = 60000) private void exportMetrics() { Metric> metric; ArrayList statusCount = new ArrayList (); for (String status : statusList) { metric = repo.findOne(status); if (metric != null) { statusCount.add(metric.getValue().intValue()); repo.reset(status); } else { statusCount.add(0); } } statusMetric.add(statusCount); } }
Обратите внимание, что мы храним количество кодов состояния в минуту .
7.4. Публичные метрики Spring Boot
Мы также можем использовать Spring Boot Public Metrics для экспорта метрик вместо использования наших собственных фильтров – следующим образом:
Во-первых, у нас есть запланированная задача экспортировать показатели в минуту :
@Autowired private MetricReaderPublicMetrics publicMetrics; private List> statusMetricsByMinute; private List statusList; private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); @Scheduled(fixedDelay = 60000) private void exportMetrics() { ArrayList lastMinuteStatuses = initializeStatuses(statusList.size()); for (Metric> counterMetric : publicMetrics.metrics()) { updateMetrics(counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add(lastMinuteStatuses); }
Нам, конечно, нужно инициализировать список кодов состояния HTTP:
private ArrayListinitializeStatuses(int size) { ArrayList counterList = new ArrayList (); for (int i = 0; i < size; i++) { counterList.add(0); } return counterList; }
А затем мы фактически обновим метрики с помощью status code count :
private void updateMetrics(Metric> counterMetric, ArrayListstatusCount) { String status = ""; int index = -1; int oldCount = 0; if (counterMetric.getName().contains("counter.status.")) { status = counterMetric.getName().substring(15, 18); // example 404, 200 appendStatusIfNotExist(status, statusCount); index = statusList.indexOf(status); oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index); statusCount.set(index, counterMetric.getValue().intValue() + oldCount); } } private void appendStatusIfNotExist(String status, ArrayList statusCount) { if (!statusList.contains(status)) { statusList.add(status); statusCount.add(0); } }
Обратите внимание, что:
- Общедоступные метрики имя счетчика состояния начинается с ” counter.status “например” counter.status.200.корень “
- Мы ведем учет количества статусов в минуту в нашем списке Показатели статуса по минутам
Мы можем экспортировать наши собранные данные, чтобы нарисовать их в виде графика – следующим образом:
public Object[][] getGraphData() { Date current = new Date(); int colCount = statusList.size() + 1; int rowCount = statusMetricsByMinute.size() + 1; Object[][] result = new Object[rowCount][colCount]; result[0][0] = "Time"; int j = 1; for (String status : statusList) { result[0][j] = status; j++; } for (int i = 1; i < rowCount; i++) { result[i][0] = dateFormat.format( new Date(current.getTime() - (60000 * (rowCount - i)))); } ListminuteOfStatuses; List last = new ArrayList (); for (int i = 1; i < rowCount; i++) { minuteOfStatuses = statusMetricsByMinute.get(i - 1); for (j = 1; j <= minuteOfStatuses.size(); j++) { result[i][j] = minuteOfStatuses.get(j - 1) - (last.size() >= j ? last.get(j - 1) : 0); } while (j < colCount) { result[i][j] = 0; j++; } last = minuteOfStatuses; } return result; }
7.5. Нарисуйте График С Использованием Метрик
Наконец, давайте представим эти метрики с помощью массива 2 измерений, чтобы затем мы могли их отобразить:
public Object[][] getGraphData() { Date current = new Date(); int colCount = statusList.size() + 1; int rowCount = statusMetric.size() + 1; Object[][] result = new Object[rowCount][colCount]; result[0][0] = "Time"; int j = 1; for (String status : statusList) { result[0][j] = status; j++; } ArrayListtemp; for (int i = 1; i < rowCount; i++) { temp = statusMetric.get(i - 1); result[i][0] = dateFormat.format (new Date(current.getTime() - (60000 * (rowCount - i)))); for (j = 1; j <= temp.size(); j++) { result[i][j] = temp.get(j - 1); } while (j < colCount) { result[i][j] = 0; j++; } } return result; }
А вот наш метод контроллера getMetricData() :
@RequestMapping(value = "/metric-graph-data", method = RequestMethod.GET) @ResponseBody public Object[][] getMetricData() { return metricService.getGraphData(); }
А вот пример ответа:
[ ["Time","counter.status.302","counter.status.200","counter.status.304"], ["2015-03-26 19:59",3,12,7], ["2015-03-26 20:00",0,4,1] ]
8. Использование Пружинного ботинка 2.Привод x
В Spring Boot 2 API приводов Spring претерпели серьезные изменения. Собственные метрики Spring были заменены на Микрометр . Итак, давайте напишем тот же пример метрики выше с Микрометром .
8.1. Замена Службы Счетчика Реестром Счетчиков
Поскольку наше приложение Spring Boot уже зависит от стартера привода, микрометр уже настроен автоматически. Мы можем ввести Реестр счетчиков вместо Службы счетчиков . Мы можем использовать различные типы Meter для сбора метрик. Счетчик является одним из счетчиков:
@Autowired private MeterRegistry registry; private ListstatusList; @Override public void increaseCount(final int status) { String counterName = "counter.status." + status; registry.counter(counterName).increment(1); if (!statusList.contains(counterName)) { statusList.add(counterName); } }
8.2. Экспорт Счетчиков С Использованием Реестра Счетчиков
В микрометре мы можем экспортировать значения Счетчика с помощью реестра Meter:
@Scheduled(fixedDelay = 60000) private void exportMetrics() { ArrayListstatusCount = new ArrayList (); for (String status : statusList) { Search search = registry.find(status); if (search != null) { Counter counter = search.counter(); statusCount.add(counter != null ? ((int) counter.count()) : 0); registry.remove(counter); } else { statusCount.add(0); } } statusMetricsByMinute.add(statusCount); }
8.3. Публикация Метрик С Использованием Счетчиков
Теперь мы также можем публиковать метрики с помощью счетчиков MeterRegistry:
@Scheduled(fixedDelay = 60000) private void exportMetrics() { ArrayListlastMinuteStatuses = initializeStatuses(statusList.size()); for (Meter counterMetric : publicMetrics.getMeters()) { updateMetrics(counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add(lastMinuteStatuses); } private void updateMetrics(final Meter counterMetric, final ArrayList statusCount) { String status = ""; int index = -1; int oldCount = 0; if (counterMetric.getId().getName().contains("counter.status.")) { status = counterMetric.getId().getName().substring(15, 18); // example 404, 200 appendStatusIfNotExist(status, statusCount); index = statusList.indexOf(status); oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index); statusCount.set(index, (int)((Counter) counterMetric).count() + oldCount); } }
9. Заключение
В этой статье мы рассмотрели несколько простых способов создания некоторых основных возможностей метрик в веб-приложении Spring.
Обратите внимание, что счетчики не являются потокобезопасными -поэтому они могут быть неточными без использования чего – то вроде атомных номеров. Это было сделано намеренно только потому, что дельта должна быть небольшой, и 100% – ная точность не является целью-скорее, выявление тенденций на ранней стадии.
Конечно, существуют более зрелые способы записи метрик HTTP в приложении, но это простой, легкий и очень полезный способ сделать это без дополнительной сложности полноценного инструмента.
Полную реализацию этой статьи можно найти в проекте GitHub .