Metrische gegevens voor uw Spring REST API

REST Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

In deze tutorial zullen we integreren basisstatistieken in een Spring REST API.

We bouwen de metrische functionaliteit eerst uit met behulp van eenvoudige servletfilters en vervolgens met behulp van een Spring Boot Actuator.

2. Het web.xml

Laten we beginnen met het registreren van een filter - "MetricFilter" - in de web.xml van onze app:

 metricFilter org.baeldung.web.metric.MetricFilter metricFilter / * 

Merk op hoe we het filter in kaart brengen om alle binnenkomende verzoeken te dekken - “/*” - die natuurlijk volledig configureerbaar is.

3. Het servletfilter

Laten we nu ons aangepaste filter maken:

openbare klasse MetricFilter implementeert Filter {privé MetricService metricService; @Override public void init (FilterConfig config) gooit ServletException {metricService = (MetricService) WebApplicationContextUtils .getRequiredWebApplicationContext (config.getServletContext ()) .getBean ("metricService"); } @Override public void doFilter (ServletRequest-verzoek, ServletResponse-antwoord, FilterChain-keten) gooit java.io.IOException, ServletException {HttpServletRequest httpRequest = ((HttpServletRequest) -verzoek); String req = httpRequest.getMethod () + "" + httpRequest.getRequestURI (); chain.doFilter (verzoek, antwoord); int status = ((HttpServletResponse) antwoord) .getStatus (); metricService.increaseCount (req, status); }}

Omdat het filter geen standaardboon is, gaan we de metricService maar haal het in plaats daarvan handmatig op - via de ServletContext.

Merk ook op dat we de uitvoering van de filterketen voortzetten door de doFilter API hier.

4. Metrisch - Aantal statuscodes

Vervolgens - laten we eens kijken naar onze simple MetricService:

@Service openbare klasse MetricService {privé ConcurrentMap statusMetric; openbare MetricService () {statusMetric = nieuwe ConcurrentHashMap (); } public void gainCount (String request, int status) {Integer statusCount = statusMetric.get (status); if (statusCount == null) {statusMetric.put (status, 1); } else {statusMetric.put (status, statusCount + 1); }} openbare kaart getStatusMetric () {return statusMetric; }}

We gebruiken een geheugensteuntje ConcurrentMap om de tellingen voor elk type HTTP-statuscode vast te houden.

Nu - om deze basisstatistiek weer te geven - gaan we deze toewijzen aan een Controller methode:

@RequestMapping (value = "/ status-metric", method = RequestMethod.GET) @ResponseBody openbare kaart getStatusMetric () {return metricService.getStatusMetric (); }

En hier is een voorbeeldantwoord:

{ "404":1, "200":6, "409":1 }

5. Metrisch - Statuscodes op verzoek

De volgende - laten we metrische gegevens opnemen voor Counts by Request:

@Service openbare klasse MetricService {private ConcurrentMap metricMap; public void gainCount (String request, int status) {ConcurrentHashMap statusMap = metricMap.get (request); if (statusMap == null) {statusMap = nieuwe ConcurrentHashMap (); } Geheel getal count = statusMap.get (status); if (count == null) {count = 1; } else {count ++; } statusMap.put (status, aantal); metricMap.put (verzoek, statusMap); } openbare kaart getFullMetric () {return metricMap; }}

We geven de metrische resultaten weer via de API:

@RequestMapping (value = "/ metric", method = RequestMethod.GET) @ResponseBody openbare kaart getMetric () {retourneer metricService.getFullMetric (); }

Hier ziet u hoe deze statistieken eruit zien:

{"GET / gebruikers": {"200": 6, "409": 1}, "GET / gebruikers / 1": {"404": 1}}

Volgens het bovenstaande voorbeeld had de API de volgende activiteit:

  • "7" vraagt ​​om "GET / gebruikers
  • "6" daarvan resulteerden in "200" statuscodereacties en slechts één in een "409"

6. Metrisch - Tijdreeksgegevens

Algehele tellingen zijn enigszins handig in een applicatie, maar als het systeem al een aanzienlijke tijd actief is - het is moeilijk te zeggen wat deze statistieken eigenlijk betekenen.

U hebt de context van tijd nodig om de gegevens te begrijpen en gemakkelijk te interpreteren.

Laten we nu een eenvoudige op tijd gebaseerde metriek bouwen; we houden het aantal statuscodes per minuut bij - als volgt:

@Service openbare klasse MetricService {private ConcurrentMap timeMap; privé statisch SimpleDateFormat dateFormat = nieuwe SimpleDateFormat ("jjjj-MM-dd HH: mm"); public void gainCount (String request, int status) {String time = dateFormat.format (new Date ()); ConcurrentHashMap statusMap = timeMap.get (tijd); if (statusMap == null) {statusMap = nieuwe ConcurrentHashMap (); } Geheel getal count = statusMap.get (status); if (count == null) {count = 1; } else {count ++; } statusMap.put (status, aantal); timeMap.put (tijd, statusMap); }}

En de getGraphData ():

openbaar object [] [] getGraphData () {int colCount = statusMetric.keySet (). size () + 1; Stel allStatus = statusMetric.keySet (); int rowCount = timeMap.keySet (). size () + 1; Object [] [] resultaat = nieuw object [rowCount] [colCount]; resultaat [0] [0] = "Tijd"; int j = 1; for (int status: allStatus) {resultaat [0] [j] = status; j ++; } int i = 1; ConcurrentMap tempMap; voor (Entry item: timeMap.entrySet ()) {resultaat [i] [0] = entry.getKey (); tempMap = entry.getValue (); voor (j = 1; j <colCount; j ++) {resultaat [i] [j] = tempMap.get (resultaat [0] [j]); if (resultaat [i] [j] == null) {resultaat [i] [j] = 0; }} i ++; } resultaat teruggeven; }

We gaan dit nu toewijzen aan de API:

@RequestMapping (value = "/ metric-graph-data", method = RequestMethod.GET) @ResponseBody openbaar object [] [] getMetricData () {return metricService.getGraphData (); }

En tot slot - we gaan het weergeven met Google Charts:

  Metrische grafiek google.load ("visualisatie", "1", {pakketten: ["corechart"]}); function drawChart () {$ .get ("/ metric-graph-data", function (mydata) {var data = google.visualization.arrayToDataTable (mydata); var options = {title: 'Website Metric', hAxis: {title : 'Time', titleTextStyle: {color: '# 333'}}, vAxis: {minValue: 0}}; var chart = new google.visualization.AreaChart (document.getElementById ('chart_div')); chart.draw ( data, opties);}); } 

7. Met Spring Boot 1.x Actuator

In de volgende secties gaan we de Actuator-functionaliteit in Spring Boot gebruiken om onze statistieken te presenteren.

Ten eerste moeten we de actuatorafhankelijkheid toevoegen aan onze pom.xml:

 org.springframework.boot spring-boot-starter-actuator 

7.1. De MetricFilter

Vervolgens kunnen we de MetricFilter - tot een echte voorjaarsboon:

@Component openbare klasse MetricFilter implementeert Filter {@Autowired privé MetricService metricService; @Override public void doFilter (ServletRequest-verzoek, ServletResponse-antwoord, FilterChain-keten) gooit java.io.IOException, ServletException {chain.doFilter (verzoek, antwoord); int status = ((HttpServletResponse) antwoord) .getStatus (); metricService.increaseCount (status); }}

Dit is natuurlijk een kleine vereenvoudiging, maar wel een die de moeite waard is om de voorheen handmatige bedrading van afhankelijkheden kwijt te raken.

7.2. Gebruik makend van CounterService

Laten we nu de CounterService om voor elke statuscode het aantal keren te tellen:

@Service openbare klasse MetricService {@Autowired privé CounterService-teller; privélijst statusList; openbare ongeldige stijgingCount (int status) {counter.increment ("status." + status); if (! statusList.contains ("counter.status." + status)) {statusList.add ("counter.status." + status); }}}

7.3. Metrische gegevens exporteren met MetricRepository

Vervolgens - we moeten de metrische gegevens exporteren - met behulp van de MetricRepository:

@Service openbare klasse MetricService {@Autowired privé MetricRepository-opslagplaats; privélijst statusMetric; privélijst statusList; @Scheduled (fixedDelay = 60000) private void exportMetrics () {Metric metric; ArrayList statusCount = nieuwe ArrayList (); voor (String status: statusList) {metric = repo.findOne (status); if (metric! = null) {statusCount.add (metric.getValue (). intValue ()); repo.reset (status); } anders {statusCount.add (0); }} statusMetric.add (statusCount); }}

Merk op dat we tellingen van statuscodes per minuut.

7.4. Spring Boot PublicMetrics

We kunnen ook Spring Boot gebruiken PublicMetrics om statistieken te exporteren in plaats van onze eigen filters te gebruiken - als volgt:

Ten eerste hebben we onze geplande taak statistieken per minuut exporteren:

@Autowired privé MetricReaderPublicMetrics publicMetrics; privélijst statusMetricsByMinute; privélijst statusList; private static final SimpleDateFormat dateFormat = nieuwe SimpleDateFormat ("jjjj-MM-dd HH: mm"); @Scheduled (fixedDelay = 60000) privé ongeldig exportMetrics () {ArrayList lastMinuteStatuses = initializeStatuses (statusList.size ()); voor (Metric counterMetric: publicMetrics.metrics ()) {updateMetrics (counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add (lastMinuteStatuses); }

We moeten natuurlijk de lijst met HTTP-statuscodes initialiseren:

private ArrayList initializeStatuses (int grootte) {ArrayList counterList = nieuwe ArrayList (); for (int i = 0; i <size; i ++) {counterList.add (0); } return counterList; }

En dan gaan we de statistieken daadwerkelijk bijwerken met statuscode tellen:

privé ongeldig updateMetrics (Metric counterMetric, ArrayList statusCount) {String status = ""; int index = -1; int oldCount = 0; if (counterMetric.getName (). bevat ("counter.status.")) {status = counterMetric.getName (). substring (15, 18); // voorbeeld 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); }}

Let daar op:

  • PublicMetics naam statusteller begint met 'counter.status" bijvoorbeeld "counter.status.200.root
  • We houden de statustelling per minuut bij in onze lijst statusMetricsByMinute

We kunnen onze verzamelde gegevens exporteren om deze in een grafiek te tekenen - als volgt:

openbaar object [] [] getGraphData () {Datum huidig ​​= nieuwe datum (); int colCount = statusList.size () + 1; int rowCount = statusMetricsByMinute.size () + 1; Object [] [] resultaat = nieuw object [rowCount] [colCount]; resultaat [0] [0] = "Tijd"; int j = 1; voor (String status: statusList) {resultaat [0] [j] = status; j ++; } for (int i = 1; i <rowCount; i ++) {resultaat [i] [0] = dateFormat.format (nieuwe datum (current.getTime () - (60000 * (rowCount - i))))); } Lijst minuteOfStatuses; Lijst laatste = nieuwe ArrayList (); voor (int i = 1; i <rowCount; i ++) {minuteOfStatuses = statusMetricsByMinute.get (i - 1); voor (j = 1; j = j? last.get (j - 1): 0); } while (j <colCount) {resultaat [i] [j] = 0; j ++; } last = minuteOfStatuses; } resultaat teruggeven; }

7.5. Teken een grafiek met behulp van statistieken

Tot slot - laten we deze statistieken weergeven via een matrix met twee dimensies - zodat we ze vervolgens in een grafiek kunnen tekenen:

openbaar object [] [] getGraphData () {Datum huidig ​​= nieuwe datum (); int colCount = statusList.size () + 1; int rowCount = statusMetric.size () + 1; Object [] [] resultaat = nieuw object [rowCount] [colCount]; resultaat [0] [0] = "Tijd"; int j = 1; voor (String status: statusList) {resultaat [0] [j] = status; j ++; } ArrayList temp; voor (int i = 1; i <rowCount; i ++) {temp = statusMetric.get (i - 1); resultaat [i] [0] = dateFormat.format (nieuwe datum (current.getTime () - (60000 * (rowCount - i)))); voor (j = 1; j <= temp.size (); j ++) {resultaat [i] [j] = temp.get (j - 1); } while (j <colCount) {resultaat [i] [j] = 0; j ++; }} resultaat retourneren; }

En hier is onze Controller-methode getMetricData ():

@RequestMapping (value = "/ metric-graph-data", method = RequestMethod.GET) @ResponseBody openbaar object [] [] getMetricData () {return metricService.getGraphData (); }

En hier is een voorbeeldantwoord:

[["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. Met Spring Boot 2.x Actuator

In Spring Boot 2 waren de API's van Spring Actuator getuige van een grote verandering. De eigen statistieken van Spring zijn vervangen door Micrometer. Dus laten we hetzelfde metrische voorbeeld hierboven schrijven met Micrometer.

8.1. Vervangen CounterService Met MeterRegistry

Omdat onze Spring Boot-applicatie al afhankelijk is van de Actuator-starter, is Micrometer al automatisch geconfigureerd. We kunnen injecteren MeterRegistry in plaats van CounterService. We kunnen verschillende soorten Meter om metrische gegevens vast te leggen. De Teller is een van de meters:

@Autowired privé MeterRegistry-register; privélijst statusList; @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. Tellingen exporteren met MeterRegistry

In Micrometer kunnen we het Teller waarden met MeterRegistry:

@Scheduled (fixedDelay = 60000) privé ongeldig exportMetrics () {ArrayList statusCount = nieuwe ArrayList (); voor (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 (teller); } anders {statusCount.add (0); }} statusMetricsByMinute.add (statusCount); }

8.3. Metrische gegevens publiceren met Meters

Nu kunnen we Metrics ook publiceren met Meters van MeterRegistry:

@Scheduled (fixedDelay = 60000) privé ongeldig exportMetrics () {ArrayList lastMinuteStatuses = initializeStatuses (statusList.size ()); voor (Meter counterMetric: publicMetrics.getMeters ()) {updateMetrics (counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add (lastMinuteStatuses); } privé ongeldig updateMetrics (laatste Meter counterMetric, laatste ArrayList statusCount) {String status = ""; int index = -1; int oldCount = 0; if (counterMetric.getId (). getName (). bevat ("counter.status.")) {status = counterMetric.getId (). getName (). substring (15, 18); // voorbeeld 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. Conclusie

In dit artikel hebben we een paar eenvoudige manieren onderzocht om enkele basismetrische mogelijkheden in een Spring-webtoepassing uit te bouwen.

Merk op dat de tellers zijn niet draadveilig - dus ze zijn misschien niet exact zonder zoiets als atoomnummers te gebruiken. Dit was opzettelijk alleen omdat de delta klein moet zijn en 100% nauwkeurigheid niet het doel is - eerder trends opsporen is dat wel.

Er zijn natuurlijk meer volwassen manieren om HTTP-statistieken in een applicatie op te nemen, maar dit is een eenvoudige, lichtgewicht en superhandige manier om het te doen zonder de extra complexiteit van een volwaardige tool.

De volledige implementatie van dit artikel is te vinden in het GitHub-project.

REST onder

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

$config[zx-auto] not found$config[zx-overlay] not found