Beregninger for vår REST API

REST Topp

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

1. Oversikt

I denne opplæringen integrerer vi grunnleggende beregninger i et REST API for våren.

Vi bygger den metriske funksjonaliteten først ved hjelp av enkle Servlet-filtre, og deretter med en Spring Boot Actuator.

2. Den web.xml

La oss starte med å registrere et filter - “MetricFilter" - inn i det web.xml av appen vår:

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

Legg merke til hvordan vi kartlegger filteret for å dekke alle forespørsler som kommer inn - “/*” - som selvfølgelig er fullt konfigurerbart.

3. Servletfilteret

Nå - la oss lage vårt tilpassede filter:

offentlig klasse MetricFilter implementerer Filter {private MetricService metricService; @ Override public void init (FilterConfig config) kaster ServletException {metricService = (MetricService) WebApplicationContextUtils .getRequiredWebApplicationContext (config.getServletContext ()) .getBean ("metricService"); } @Override offentlig ugyldig doFilter (ServletRequest-forespørsel, ServletResponse-svar, FilterChain-kjede) kaster java.io.IOException, ServletException {HttpServletRequest httpRequest = ((HttpServletRequest) forespørsel); Streng req = httpRequest.getMethod () + "" + httpRequest.getRequestURI (); chain.doFilter (forespørsel, svar); int status = (((HttpServletResponse) respons) .getStatus (); metricService.increaseCount (req, status); }}

Siden filteret ikke er en vanlig bønne, skal vi ikke injisere metricService men i stedet hente den manuelt - via ServletContext.

Vær også oppmerksom på at vi fortsetter kjøringen av filterkjeden ved å ringe doFilter API her.

4. Metrisk - Statuskodetall

Neste - la oss ta en titt på det enkle MetricService:

@Service offentlig klasse MetricService {privat ConcurrentMap statusMetric; public MetricService () {statusMetric = new ConcurrentHashMap (); } offentlig tomrom økeCount (strengforespørsel, int status) {Integer statusCount = statusMetric.get (status); hvis (statusCount == null) {statusMetric.put (status, 1); } annet {statusMetric.put (status, statusCount + 1); }} offentlig kart getStatusMetric () {retur statusMetric; }}

Vi bruker en i minnet ConcurrentMap for å holde teller for hver type HTTP-statuskode.

Nå - for å vise denne grunnleggende beregningen - skal vi kartlegge den til en Kontroller metode:

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

Og her er et eksempel på svar:

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

5. Metrisk - statuskoder etter forespørsel

Neste - la oss registrere beregninger for tellinger etter forespørsel:

@Service offentlig klasse MetricService {privat ConcurrentMap metricMap; offentlig tomrom økeCount (strengforespørsel, int-status) {ConcurrentHashMap statusMap = metricMap.get (forespørsel); hvis (statusMap == null) {statusMap = ny ConcurrentHashMap (); } Heltall = statusMap.get (status); hvis (count == null) {count = 1; } annet {count ++; } statusMap.put (status, count); metricMap.put (forespørsel, statusMap); } offentlig kart getFullMetric () {return metricMap; }}

Vi viser beregningsresultatene via API: et:

@RequestMapping (value = "/ metric", method = RequestMethod.GET) @ResponseBody public Map getMetric () {return metricService.getFullMetric (); }

Slik ser disse beregningene ut:

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

I henhold til eksemplet ovenfor hadde API følgende aktivitet:

  • "7" forespørsler til "GET / brukere
  • “6” av dem resulterte i “200” statuskodesvar og bare én i en “409”

6. Metrisk - tidsseriedata

Totaltall er noe nyttig i en applikasjon, men hvis systemet har kjørt i betydelig tid - det er vanskelig å fortelle hva disse beregningene faktisk betyr.

Du trenger tidssammenheng for at dataene skal gi mening og lett tolkes.

La oss nå bygge en enkel tidsbasert beregning; vi vil føre oversikt over antall statuskoder per minutt - som følger:

@Service offentlig klasse MetricService {privat ConcurrentMap timeMap; privat statisk SimpleDateFormat dateFormat = ny SimpleDateFormat ("åååå-MM-dd HH: mm"); public void increaseCount (String request, int status) {String time = dateFormat.format (new Date ()); ConcurrentHashMap statusMap = timeMap.get (tid); hvis (statusMap == null) {statusMap = ny ConcurrentHashMap (); } Heltall = statusMap.get (status); hvis (count == null) {count = 1; } annet {count ++; } statusMap.put (status, count); timeMap.put (time, statusMap); }}

Og getGraphData ():

offentlig objekt [] [] getGraphData () {int colCount = statusMetric.keySet (). størrelse () + 1; Sett allStatus = statusMetric.keySet (); int rowCount = timeMap.keySet (). størrelse () + 1; Objekt [] [] resultat = nytt objekt [rowCount] [colCount]; resultat [0] [0] = "Tid"; int j = 1; for (int status: allStatus) {resultat [0] [j] = status; j ++; } int i = 1; ConcurrentMap tempMap; for (Entry oppføring: timeMap.entrySet ()) {resultat [i] [0] = entry.getKey (); tempMap = entry.getValue (); for (j = 1; j <colCount; j ++) {resultat [i] [j] = tempMap.get (resultat [0] [j]); hvis (resultat [i] [j] == null) {resultat [i] [j] = 0; }} i ++; } returnere resultat; }

Vi skal nå kartlegge dette til API: et:

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

Og til slutt - vi skal gjengi det ved hjelp av Google Charts:

  Metrisk graf google.load ("visualisering", "1", {pakker: ["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, opsjoner);}); } 

7. Bruke Spring Boot 1.x aktuator

I de neste par seksjonene skal vi koble oss til aktuatorfunksjonaliteten i Spring Boot for å presentere beregningene våre.

Først - vi må legge til aktuatoravhengigheten til vår pom.xml:

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

7.1. De MetricFilter

Neste - vi kan snu MetricFilter - til en faktisk vårbønne:

@Component public class MetricFilter implementerer filter {@Autowired private MetricService metricService; @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) kaster java.io.IOException, ServletException {chain.doFilter (forespørsel, svar); int status = (((HttpServletResponse) respons) .getStatus (); metricService.increaseCount (status); }}

Dette er selvfølgelig en mindre forenkling - men en som er verdt å gjøre for å bli kvitt den tidligere manuelle kablingen av avhengigheter.

7.2. Ved hjelp av CounterService

La oss nå bruke CounterService for å telle forekomster for hver statuskode:

@Service offentlig klasse MetricService {@Autowired privat CounterService-teller; privat Liste statusListe; public void increaseCount (int status) {counter.increment ("status." + status); hvis (! statusList.contains ("counter.status." + status)) {statusList.add ("counter.status." + status); }}}

7.3. Eksporter beregninger ved hjelp av MetricRepository

Neste - vi må eksportere beregningene - ved hjelp av MetricRepository:

@Service offentlig klasse MetricService {@Autowired privat MetricRepository repo; privat liste statusMetrik; privat Liste statusListe; @Scheduled (fixedDelay = 60000) private void exportMetrics () {Metrisk beregning; ArrayList statusCount = ny ArrayList (); for (String status: statusList) {metric = repo.findOne (status); hvis (metrisk! = null) {statusCount.add (metric.getValue (). intValue ()); repo.reset (status); } annet {statusCount.add (0); }} statusMetric.add (statusCount); }}

Merk at vi lagrer antall statuskoder per minutt.

7.4. Vårstøvel PublicMetrics

Vi kan også bruke Spring Boot PublicMetrics for å eksportere beregninger i stedet for å bruke våre egne filtre - som følger:

Først har vi vår planlagte oppgave å eksporter beregninger per minutt:

@Autowired private MetricReaderPublicMetrics publicMetrics; privat liste statusMetricsByMinute; privat Liste statusListe; privat statisk endelig SimpleDateFormat dateFormat = ny SimpleDateFormat ("åååå-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); }

Vi må selvfølgelig initialisere listen over HTTP-statuskoder:

private ArrayList initializeStatuses (int størrelse) {ArrayList counterList = new ArrayList (); for (int i = 0; i <størrelse; i ++) {counterList.add (0); } returner counterList; }

Og så skal vi faktisk oppdatere beregningene med antall statuskoder:

privat tomrom updateMetrics (Metric counterMetric, ArrayList statusCount) {String status = ""; int-indeks = -1; int oldCount = 0; hvis (counterMetric.getName (). inneholder ("counter.status.")) {status = counterMetric.getName (). substring (15, 18); // eksempel 404, 200 appendStatusIfNotExist (status, statusCount); indeks = statusList.indexOf (status); oldCount = statusCount.get (index) == null? 0: statusCount.get (indeks); statusCount.set (indeks, counterMetric.getValue (). intValue () + oldCount); }} privat tomrom appendStatusIfNotExist (String status, ArrayList statusCount) {if (! statusList.contains (status)) {statusList.add (status); statusCount.add (0); }}

Noter det:

  • PublicMetics status tellernavn start med “counter.status" for eksempel "counter.status.200.root
  • Vi registrerer statusantall per minutt i listen vår statusMetricsByMinute

Vi kan eksportere dataene vi har samlet inn for å tegne dem i en graf - som følger:

public Object [] [] getGraphData () {Date current = new Date (); int colCount = statusList.size () + 1; int rowCount = statusMetricsByMinute.size () + 1; Objekt [] [] resultat = nytt objekt [rowCount] [colCount]; resultat [0] [0] = "Tid"; 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)))); } Liste minuteOfStatuses; Liste sist = ny ArrayList (); for (int i = 1; i <rowCount; i ++) {minuteOfStatuses = statusMetricsByMinute.get (i - 1); for (j = 1; j = j? siste.get (j - 1): 0); } mens (j <colCount) {resultat [i] [j] = 0; j ++; } siste = minuttOfStatuses; } returnere resultat; }

7.5. Tegn graf ved hjelp av beregninger

Til slutt - la oss representere disse beregningene via en 2-dimensjonsmatrise - slik at vi kan tegne dem:

public Object [] [] getGraphData () {Date current = new Date (); int colCount = statusList.size () + 1; int rowCount = statusMetric.size () + 1; Objekt [] [] resultat = nytt objekt [rowCount] [colCount]; resultat [0] [0] = "Tid"; int j = 1; for (String status: statusList) {result [0] [j] = status; j ++; } ArrayList temp; for (int i = 1; i <rowCount; i ++) {temp = statusMetric.get (i - 1); resultat [i] [0] = dateFormat.format (ny dato (current.getTime () - (60000 * (rowCount - i)))); for (j = 1; j <= temp.størrelse (); j ++) {resultat [i] [j] = temp.get (j - 1); } mens (j <colCount) {resultat [i] [j] = 0; j ++; }} returner resultat; }

Og her er vår Controller-metode getMetricData ():

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

Og her er et eksempel på svar:

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

I Spring Boot 2 var APIer for Spring Actuator vitne til en stor endring. Vårens egne beregninger er erstattet med Mikrometer. Så la oss skrive det samme beregningseksemplet ovenfor med Mikrometer.

8.1. Erstatte CounterService Med MeterRegistry

Ettersom vår Spring Boot-applikasjon allerede er avhengig av aktuatorstarteren, er Micrometer allerede konfigurert automatisk. Vi kan injisere MeterRegistry i stedet for CounterService. Vi kan bruke forskjellige typer Måler for å fange beregninger. De Disk er et av målerne:

@Autowired privat MeterRegistry register; privat Liste statusListe; @ Overstyr offentlig ugyldig økningstall (endelig int-status) {String counterName = "counter.status." + status; registry.counter (counterName) .increment (1); hvis (! statusList.contains (counterName)) {statusList.add (counterName); }}

8.2. Eksporterer tellinger ved hjelp av MeterRegistry

I Micrometer kan vi eksportere Disk verdier ved hjelp av MeterRegistry:

@Scheduled (fixedDelay = 60000) private void exportMetrics () {ArrayList statusCount = 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); } annet {statusCount.add (0); }} statusMetricsByMinute.add (statusCount); }

8.3. Publiseringsmålinger ved hjelp av Meter

Nå kan vi også publisere beregninger ved hjelp av MeterRegistry's Meter:

@Scheduled (fixedDelay = 60000) private void exportMetrics () {ArrayList lastMinuteStatuses = initializeStatuses (statusList.size ()); for (Meter counterMetric: publicMetrics.getMeters ()) {updateMetrics (counterMetric, lastMinuteStatuses); } statusMetricsByMinute.add (lastMinuteStatuses); } privat tomrom updateMetrics (final Meter counterMetric, final ArrayList statusCount) {String status = ""; int-indeks = -1; int oldCount = 0; hvis (counterMetric.getId (). getName (). inneholder ("counter.status.")) {status = counterMetric.getId (). getName (). substring (15, 18); // eksempel 404, 200 appendStatusIfNotExist (status, statusCount); indeks = statusList.indexOf (status); oldCount = statusCount.get (index) == null? 0: statusCount.get (indeks); statusCount.set (index, (int) ((Counter) counterMetric) .count () + oldCount); }}

9. Konklusjon

I denne artikkelen undersøkte vi noen få enkle måter å bygge ut noen grunnleggende beregningsfunksjoner til et vårprogram.

Merk at tellerne er ikke trådsikker - så de er kanskje ikke nøyaktige uten å bruke noe som atomnummer. Dette var bevisst bare fordi deltaet skulle være lite og 100% nøyaktighet ikke er målet - det er heller å oppdage trender tidlig.

Det er selvfølgelig mer modne måter å registrere HTTP-beregninger i en applikasjon, men dette er en enkel, lett og supernyttig måte å gjøre det uten ekstra kompleksitet av et fullverdig verktøy.

Den fulle implementeringen av denne artikkelen finner du i GitHub-prosjektet.

HVILLE bunnen

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

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