Статьи

Метрики, метрики везде

С DevOps метрики начинают быть среди нефункциональных требований, которые любое приложение должно принести в область. Прежде чем идти дальше, я хотел бы сделать несколько замечаний:

  1. Метрики не только о нефункциональных вещах. Многие показатели представляют очень важный KPI для бизнеса. Например, для интернет-магазина бизнес должен знать, сколько клиентов покидают процесс оформления заказа и на каком экране. Правда, есть несколько решений для достижения этой цели, хотя все они основаны на веб-технологиях (Google Analytics приходит на ум) и метрики могут также потребоваться для разных архитектур. И наличие всех метрик в одном бэкэнде означает, что их можно легко соотнести.
  2. Метрики, как и любая другая NFR ( например,  регистрация и обработка исключений), должны разрабатываться и управляться заранее, а не выдвигаться в качестве запоздалой мысли. Откуда я это знаю? Ну, один из моих последних проектов был сосредоточен только на функциональных требованиях, и только в конце концов руководство проекта осознало важность NFR. Поверьте мне, когда я говорю, что это было ужасно — и это стоило намного дороже, чем если бы оно было разработано на ранних этапах проекта.
  3. Метрики имеют накладные расходы. Однако без метрик невозможно повысить производительность. Просто прими это и живи с этим.

Входные данные следующие: приложение  основано на Spring MVC, а показатели должны быть агрегированы в  графите . Мы начнем с использования отличного  проекта Metrics : он не только выполняет свою работу, его  документация  очень высокого качества и доступна под дружественной лицензией OpenSource Apache v2.0.

Тем не менее, давайте представим «стандартную» базовую архитектуру для управления этими компонентами.

Во-первых, хотя Metrics предлагает конечную точку Graphite, это требует настройки в каждой среде, что усложняет работу, особенно на рабочих станциях разработчиков. Для этого мы отправим метрики в JMX и представим  jmxtrans  в качестве промежуточного компонента между JMX и графитом. Поскольку каждая JVM предоставляет службы JMX, это не требует настройки, когда в этом нет необходимости, и не влияет на производительность.

Во-вторых, как разработчики, нам обычно нравится разрабатывать все с нуля, чтобы показать, насколько мы хороши — или иногда потому, что они не просматривали документацию. Моя точка зрения, как инженера-программиста, заключается в том, что я бы не стал изобретать велосипед и сосредоточиться на задаче в конце. Фактически Spring Boot уже  интегрируется с Metrics  через компонент Actuator. Однако он предоставляет только GaugeService — для отправки уникальных значений, а CounterService — для увеличения / уменьшения значений. Это может быть достаточно для FR, но не для NFR, поэтому мы могли бы немного подправить.

Поток будет выглядеть так: Код> Spring Boot> Метрики> JMX> Графит

Отправной точкой является создание аспекта, поскольку показатель производительности является сквозной задачей:

@Aspect
public class MetricAspect {
private final MetricSender metricSender;
@Autowired
public MetricAspect(MetricSender metricSender) {
this.metricSender = metricSender;
}
@Around("execution(* ch.frankel.blog.metrics.ping..*(..)) ||execution(* ch.frankel.blog.metrics.dice..*(..))")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = metricSender.getStartedStopWatch();
try {
return pjp.proceed();
} finally {
Class<?> clazz = pjp.getTarget().getClass();
String methodName = pjp.getSignature().getName();
metricSender.stopAndSend(stopWatch, clazz, methodName);
}
}
}

Единственное, что выходит за рамки обычного — это использование автопроводки, так как аспекты, кажется, не могут быть целью явной разводки (пока?). Также обратите внимание, что сам аспект не взаимодействует с Metrics API, он делегирует только выделенному компоненту:

public class MetricSender {
private final MetricRegistry registry;
public MetricSender(MetricRegistry registry) {
this.registry = registry;
}
private Histogram getOrAdd(String metricsName) {
Map<String, Histogram> registeredHistograms = registry.getHistograms();
Histogram registeredHistogram = registeredHistograms.get(metricsName);
if (registeredHistogram == null) {
Reservoir reservoir = new ExponentiallyDecayingReservoir();
registeredHistogram = new Histogram(reservoir);
registry.register(metricsName, registeredHistogram);
}
return registeredHistogram;
}
public StopWatch getStartedStopWatch() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
return stopWatch;
}
private String computeMetricName(Class<?> clazz, String methodName) {
return clazz.getName() + '.' + methodName;
}
public void stopAndSend(StopWatch stopWatch, Class<?> clazz, String methodName) {
stopWatch.stop();
String metricName = computeMetricName(clazz, methodName);
getOrAdd(metricName).update(stopWatch.getTotalTimeMillis());
}
}

Отправитель делает несколько интересных вещей (но без состояния):

  • Возвращает новый  StopWatch для аспекта, чтобы передать обратно после выполнения метода
  • Он вычисляет имя метрики в зависимости от класса и метода
  • Он останавливает  StopWatch и отправляет время MetricRegistry
  • Обратите внимание , что также лениво создает и регистрирует новый  Histogram с  ExponentiallyDecayingReservoir экземпляром. Поведение по умолчанию — предоставить  UniformReservoir, который хранит данные вечно и не подходит для наших нужд.

Последний шаг — сообщить Metrics API о необходимости отправки данных в JMX. Это можно сделать в одном из классов конфигурации, предпочтительно в классе, предназначенном для метрик, с использованием  @PostConstruct аннотации для желаемого метода.

@Configuration
public class MetricsConfiguration {
@Autowired
private MetricRegistry metricRegistry;
@Bean
public MetricSender metricSender() {
return new MetricSender(metricRegistry);
}
@PostConstruct
public void connectRegistryToJmx() {
JmxReporter reporter = JmxReporter.forRegistry(metricRegistry).build();
reporter.start();
}
}

JConsole должна выглядеть следующим образом. Обледенение, все метрики Spring Boot по умолчанию также доступны:

Источники для этой статьи  доступны  в «формате» Maven.

Чтобы идти дальше: