Introdução ao Netflix Servo
1. Visão geral
Netflix Servo é uma ferramenta de métrica para aplicativos Java. Servo é semelhante aDropwizard Metrics, mas muito mais simples. Ele utiliza o JMX apenas para fornecer uma interface simples para expor e publicar métricas de aplicativos.
Neste artigo, apresentaremos o que o Servo fornece e como podemos usá-lo para coletar e publicar métricas de aplicativos.
2. Dependências do Maven
Antes de mergulharmos na implementação real, vamos adicionar a dependênciaServo ao arquivopom.xml:
com.netflix.servo
servo-core
0.12.16
Além disso, existem muitas extensões disponíveis, comoServo-Apache,Servo-AWS, etc. Podemos precisar deles mais tarde. As versões mais recentes dessas extensões também podem ser encontradas emMaven Central.
3. Coletar métricas
Primeiro, vamos ver como coletar métricas de nosso aplicativo.
Servo fornece quatro tipos de métricas primárias:Counter,Gauge,Timer,eInformational.
3.1. Tipos de métrica - Contador
Counters são usados para registrar a incrementação. Implementações comumente usadas sãoBasicCounter,StepCounter ePeakRateCounter.
BasicCounter faz o que um contador deve fazer, simples e direto:
Counter counter = new BasicCounter(MonitorConfig.builder("test").build());
assertEquals("counter should start with 0", 0, counter.getValue().intValue());
counter.increment();
assertEquals("counter should have increased by 1", 1, counter.getValue().intValue());
counter.increment(-1);
assertEquals("counter should have decreased by 1", 0, counter.getValue().intValue());
PeakRateCounter retorna a contagem máxima para um determinado segundo durante o intervalo de pesquisa:
Counter counter = new PeakRateCounter(MonitorConfig.builder("test").build());
assertEquals(
"counter should start with 0",
0, counter.getValue().intValue());
counter.increment();
SECONDS.sleep(1);
counter.increment();
counter.increment();
assertEquals("peak rate should have be 2", 2, counter.getValue().intValue());
Ao contrário de outros contadores,StepCounter registra a taxa por segundo do intervalo de pesquisa anterior:
System.setProperty("servo.pollers", "1000");
Counter counter = new StepCounter(MonitorConfig.builder("test").build());
assertEquals("counter should start with rate 0.0", 0.0, counter.getValue());
counter.increment();
SECONDS.sleep(1);
assertEquals(
"counter rate should have increased to 1.0",
1.0, counter.getValue());
Observe que definimosservo.pollers para1000 no código acima. Isso era para definir o intervalo de pesquisa para 1 segundo em vez de intervalos de 60 segundos e 10 segundos por padrão. Cobriremos mais sobre isso mais tarde.
3.2. Tipos de métrica - Calibre
Gauge é um monitor simples que retorna o valor atual. BasicGauge,MinGauge,MaxGauge eNumberGauges são fornecidos.
BasicGauge invoca aCallable para obter o valor atual. Podemos obter o tamanho de uma coleção, o último valor deBlockingQueue ou qualquer valor que requeira pequenos cálculos.
Gauge gauge = new BasicGauge<>(MonitorConfig.builder("test")
.build(), () -> 2.32);
assertEquals(2.32, gauge.getValue(), 0.01);
MaxGauge eMinGauge são usados para manter o controle dos valores máximo e mínimo, respectivamente:
MaxGauge gauge = new MaxGauge(MonitorConfig.builder("test").build());
assertEquals(0, gauge.getValue().intValue());
gauge.update(4);
assertEquals(4, gauge.getCurrentValue(0));
gauge.update(1);
assertEquals(4, gauge.getCurrentValue(0));
NumberGauge (LongGauge,DoubleGauge) envolve um fornecidoNumber (Long,Double). Para coletar métricas usando esses medidores, devemos garantir queNumber seja seguro para threads.
3.3. Tipos de métrica - Cronômetro
Timers ajuda a medir a duração de um evento específico. As implementações padrão sãoBasicTimer,StatsTimer eBucketTimer.
BasicTimer registra o tempo total, contagem e outras estatísticas simples:
BasicTimer timer = new BasicTimer(MonitorConfig.builder("test").build(), SECONDS);
Stopwatch stopwatch = timer.start();
SECONDS.sleep(1);
timer.record(2, SECONDS);
stopwatch.stop();
assertEquals("timer should count 1 second", 1, timer.getValue().intValue());
assertEquals("timer should count 3 seconds in total",
3.0, timer.getTotalTime(), 0.01);
assertEquals("timer should record 2 updates", 2, timer.getCount().intValue());
assertEquals("timer should have max 2", 2, timer.getMax(), 0.01);
StatsTimer fornece estatísticas muito mais ricas por amostragem entre os intervalos de votação:
System.setProperty("netflix.servo", "1000");
StatsTimer timer = new StatsTimer(MonitorConfig
.builder("test")
.build(), new StatsConfig.Builder()
.withComputeFrequencyMillis(2000)
.withPercentiles(new double[] { 99.0, 95.0, 90.0 })
.withPublishMax(true)
.withPublishMin(true)
.withPublishCount(true)
.withPublishMean(true)
.withPublishStdDev(true)
.withPublishVariance(true)
.build(), SECONDS);
Stopwatch stopwatch = timer.start();
SECONDS.sleep(1);
timer.record(3, SECONDS);
stopwatch.stop();
stopwatch = timer.start();
timer.record(6, SECONDS);
SECONDS.sleep(2);
stopwatch.stop();
assertEquals("timer should count 12 seconds in total",
12, timer.getTotalTime());
assertEquals("timer should count 12 seconds in total",
12, timer.getTotalMeasurement());
assertEquals("timer should record 4 updates", 4, timer.getCount());
assertEquals("stats timer value time-cost/update should be 2",
3, timer.getValue().intValue());
final Map metricMap = timer.getMonitors().stream()
.collect(toMap(monitor -> getMonitorTagValue(monitor, "statistic"),
monitor -> (Number) monitor.getValue()));
assertThat(metricMap.keySet(), containsInAnyOrder(
"count", "totalTime", "max", "min", "variance", "stdDev", "avg",
"percentile_99", "percentile_95", "percentile_90"));
BucketTimer fornece uma maneira de obter a distribuição de amostras por intervalos de valores de intervalo:
BucketTimer timer = new BucketTimer(MonitorConfig
.builder("test")
.build(), new BucketConfig.Builder()
.withBuckets(new long[] { 2L, 5L })
.withTimeUnit(SECONDS)
.build(), SECONDS);
timer.record(3);
timer.record(6);
assertEquals(
"timer should count 9 seconds in total",
9, timer.getTotalTime().intValue());
Map metricMap = timer.getMonitors().stream()
.filter(monitor -> monitor.getConfig().getTags().containsKey("servo.bucket"))
.collect(toMap(
m -> getMonitorTagValue(m, "servo.bucket"),
m -> (Long) m.getValue()));
assertThat(metricMap, allOf(hasEntry("bucket=2s", 0L), hasEntry("bucket=5s", 1L),
hasEntry("bucket=overflow", 1L)));
Para rastrear operações de longa duração que podem durar horas, podemos usar o monitor compostoDurationTimer.
3.4. Tipos de métrica - Informativo
Além disso, podemos usar o monitorInformational para registrar informações descritivas para ajudar na depuração e diagnóstico. A única implementação éBasicInformational, e seu uso não pode ser mais simples:
BasicInformational informational = new BasicInformational(
MonitorConfig.builder("test").build());
informational.setValue("information collected");
3.5. MonitorRegistry
Os tipos de métrica são todos do tipoMonitor, que é a base deServo. Agora sabemos que tipos de ferramentas coletam métricas brutas, mas para relatar os dados, precisamos registrar esses monitores.
Observe que cada monitor configurado único deve ser registrado uma vez e apenas uma vez para garantir a correção das métricas. Para que possamos registrar os monitores usando o padrão Singleton.
Na maioria das vezes, podemos usarDefaultMonitorRegistry para registrar monitores:
Gauge gauge = new BasicGauge<>(MonitorConfig.builder("test")
.build(), () -> 2.32);
DefaultMonitorRegistry.getInstance().register(gauge);
Se quisermos registrar um monitor dinamicamente,DynamicTimer eDynamicCounter podem ser usados:
DynamicCounter.increment("monitor-name", "tag-key", "tag-value");
Observe que o registro dinâmico causaria uma operação de pesquisa cara cada vez que o valor fosse atualizado.
Servo também fornece vários métodos auxiliares para registrar monitores declarados em objetos:
Monitors.registerObject("testObject", this);
assertTrue(Monitors.isObjectRegistered("testObject", this));
O métodoregisterObject usará reflexão para adicionar todas as instâncias deMonitors declaradas pela anotação@Monitor e adicionar marcas declaradas por@MonitorTags:
@Monitor(
name = "integerCounter",
type = DataSourceType.COUNTER,
description = "Total number of update operations.")
private AtomicInteger updateCount = new AtomicInteger(0);
@MonitorTags
private TagList tags = new BasicTagList(
newArrayList(new BasicTag("tag-key", "tag-value")));
@Test
public void givenAnnotatedMonitor_whenUpdated_thenDataCollected() throws Exception {
System.setProperty("servo.pollers", "1000");
Monitors.registerObject("testObject", this);
assertTrue(Monitors.isObjectRegistered("testObject", this));
updateCount.incrementAndGet();
updateCount.incrementAndGet();
SECONDS.sleep(1);
List> metrics = observer.getObservations();
assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
Iterator> metricIterator = metrics.iterator();
metricIterator.next(); //skip first empty observation
while (metricIterator.hasNext()) {
assertThat(metricIterator.next(), hasItem(
hasProperty("config",
hasProperty("name", is("integerCounter")))));
}
}
4. Publicar métricas
Com as métricas coletadas, podemos publicá-las em qualquer formato, como renderizar gráficos de séries temporais em várias plataformas de visualização de dados. Para publicar as métricas, precisamos pesquisar os dados periodicamente a partir das observações do monitor.
4.1. MetricPoller
MetricPoller é usado como um coletor de métricas. Podemos buscar métricas deMonitorRegistries,JVM,JMX. Com a ajuda de extensões, podemos pesquisar métricas comoApache server statuseTomcat metrics.
MemoryMetricObserver observer = new MemoryMetricObserver();
PollRunnable pollRunnable = new PollRunnable(new JvmMetricPoller(),
new BasicMetricFilter(true), observer);
PollScheduler.getInstance().start();
PollScheduler.getInstance().addPoller(pollRunnable, 1, SECONDS);
SECONDS.sleep(1);
PollScheduler.getInstance().stop();
List> metrics = observer.getObservations();
assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
List keys = extractKeys(metrics);
assertThat(keys, hasItems("loadedClassCount", "initUsage", "maxUsage", "threadCount"));
Aqui, criamos umJvmMetricPoller para pesquisar métricas de JVM. Ao adicionar o pesquisador ao agendador, deixamos a tarefa de pesquisa ser executada a cada segundo. As configurações de poller padrão do sistema são definidas emPollers, mas podemos especificar os pollers para usar com a propriedade de sistemaservo.pollers.
4.2. MetricObserver
Ao pesquisar métricas, as observações deMetricObservers registradas serão atualizadas.
MetricObservers fornecidos por padrão sãoMemoryMetricObserver,FileMetricObserver eAsyncMetricObserver. Já mostramos como usarMemoryMetricObserver no exemplo de código anterior.
Atualmente, várias extensões úteis estão disponíveis:
-
AtlasMetricObserver: publica métricas emNetflix Atlas para gerar dados de série temporal na memória para análise
-
CloudWatchMetricObserver: envia métricas paraAmazon CloudWatch para monitoramento e rastreamento de métricas
-
GraphiteObserver: publique métricas emGraphite para armazenar e representar graficamente
Podemos implementar umMetricObserver personalizado para publicar as métricas do aplicativo onde acharmos adequado. A única coisa a se preocupar é lidar com as métricas atualizadas:
public class CustomObserver extends BaseMetricObserver {
//...
@Override
public void updateImpl(List metrics) {
//TODO
}
}
4.3. Publicar no Netflix Atlas
Atlas é outra ferramenta relacionada a métricas da Netflix. É uma ferramenta para gerenciar dados dimensionais de série temporal, que é um lugar perfeito para publicar as métricas que coletamos.
Agora, vamos demonstrar como publicar nossas métricas no Netflix Atlas.
Primeiro, vamos acrescentar a dependênciaservo-atlas aopom.xml:
com.netflix.servo
servo-atlas
${netflix.servo.ver}
0.12.17
Essa dependência incluiAtlasMetricObserver para nos ajudar a publicar métricas paraAtlas.
Em seguida, configuraremos um servidor Atlas:
$ curl -LO 'https://github.com/Netflix/atlas/releases/download/v1.4.4/atlas-1.4.4-standalone.jar'
$ curl -LO 'https://raw.githubusercontent.com/Netflix/atlas/v1.4.x/conf/memory.conf'
$ java -jar atlas-1.4.4-standalone.jar memory.conf
Para economizar nosso tempo para o teste, vamos definir o tamanho do passo para 1 segundo emmemory.conf, para que possamos gerar um gráfico de série temporal com detalhes suficientes das métricas.
OAtlasMetricObserver requer uma configuração simples e uma lista de tags. As métricas das tags fornecidas serão enviadas para o Atlas:
System.setProperty("servo.pollers", "1000");
System.setProperty("servo.atlas.batchSize", "1");
System.setProperty("servo.atlas.uri", "http://localhost:7101/api/v1/publish");
AtlasMetricObserver observer = new AtlasMetricObserver(
new BasicAtlasConfig(), BasicTagList.of("servo", "counter"));
PollRunnable task = new PollRunnable(
new MonitorRegistryMetricPoller(), new BasicMetricFilter(true), observer);
Depois de iniciar umPollScheduler com a tarefaPollRunnable, podemos publicar métricas no Atlas automaticamente:
Counter counter = new BasicCounter(MonitorConfig
.builder("test")
.withTag("servo", "counter")
.build());
DefaultMonitorRegistry
.getInstance()
.register(counter);
assertThat(atlasValuesOfTag("servo"), not(containsString("counter")));
for (int i = 0; i < 3; i++) {
counter.increment(RandomUtils.nextInt(10));
SECONDS.sleep(1);
counter.increment(-1 * RandomUtils.nextInt(10));
SECONDS.sleep(1);
}
assertThat(atlasValuesOfTag("servo"), containsString("counter"));
Com base nas métricas, podemos gerar um gráfico de linha usandograph API do Atlas:
5. Sumário
Neste artigo, apresentamos como usar o Netflix Servo para coletar e publicar métricas de aplicativos.
Caso você não tenha lido nossa introdução ao Dropwizard Metrics, verifiquehere para uma rápida comparação com o Servo.
Como sempre, o código de implementação completo deste artigo pode ser encontradoover on Github.