Metrik untuk Spring REST API anda

REST Teratas

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS

1. Gambaran keseluruhan

Dalam tutorial ini, kami akan mengintegrasikan Metrik asas ke Spring REST API .

Kami akan membina fungsi metrik terlebih dahulu menggunakan Servlet Filters sederhana, kemudian menggunakan Spring Boot Actuator.

2. Laman web.xml

Mari mulakan dengan mendaftarkan penapis - " MetricFilter " - ke dalam web.xml aplikasi kami:

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

Perhatikan bagaimana kami memetakan penapis untuk menutup semua permintaan yang masuk - “/ *” - yang tentunya dapat dikonfigurasi sepenuhnya.

3. Penapis Servlet

Sekarang - mari buat penapis tersuai kami:

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); } }

Oleh kerana penapis bukan kacang standard, kami tidak akan menyuntikkan metrikService melainkan mengambilnya secara manual - melalui ServletContext .

Perhatikan juga bahawa kami meneruskan pelaksanaan rantai penapis dengan memanggil doFilter API di sini.

4. Metrik - Kiraan Kod Status

Seterusnya - mari lihat MetricService ringkas kami :

@Service public class MetricService { private ConcurrentMap statusMetric; 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; } }

Kami menggunakan dalam memori ConcurrentMap untuk menahan kiraan bagi setiap jenis kod status HTTP.

Sekarang - untuk memaparkan metrik asas ini - kami akan memetakannya ke kaedah Pengawal :

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

Berikut adalah contoh jawapan:

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

5. Metrik - Kod Status mengikut Permintaan

Seterusnya - mari rakam metrik untuk Kiraan mengikut Permintaan :

@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; } }
    

Kami akan memaparkan hasil metrik melalui API:

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

Begini rupa metrik ini:

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

Menurut contoh di atas, API mempunyai aktiviti berikut:

  • Permintaan "7" untuk "DAPATKAN / pengguna "
  • "6" di antaranya menghasilkan respons kod status "200" dan hanya satu di "409"

6. Metrik - Data Siri Masa

Kiraan keseluruhan agak berguna dalam aplikasi, tetapi jika sistem telah berjalan dalam jangka masa yang panjang - sukar untuk mengetahui maksud sebenarnya metrik ini .

Anda memerlukan konteks masa agar data masuk akal dan dapat ditafsirkan dengan mudah.

Sekarang mari kita membina metrik berdasarkan masa yang mudah; kami akan menyimpan catatan jumlah kod status seminit - seperti berikut:

@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); } }
    

Dan getGraphData () :

public Object[][] getGraphData() { int colCount = statusMetric.keySet().size() + 1; Set allStatus = 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; }
    

Kami sekarang akan memetakan ini ke API:

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

Dan akhirnya - kami akan menghasilkannya menggunakan Carta Google:

  Metric Graph    google.load("visualization", "1", {packages : [ "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, options); }); } 

7. Menggunakan Spring Boot 1.x Actuator

Dalam beberapa bahagian seterusnya, kita akan menyambung ke fungsi Penggerak di Spring Boot untuk membentangkan metrik kami.

Pertama - kita perlu menambahkan kebergantungan penggerak ke pom.xml kami :

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

7.1. The MetricFilter

Seterusnya - kita boleh mengubah MetricFilter - menjadi kacang musim bunga sebenar:

@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); } }

Sudah tentu, ini adalah penyederhanaan kecil - tetapi yang perlu dilakukan untuk menghilangkan pergantungan kabel sebelumnya.

7.2. Menggunakan CounterService

Sekarang mari kita menggunakan CounterService untuk mengira kejadian bagi setiap Kod Status:

@Service public class MetricService { @Autowired private CounterService counter; private List statusList; public void increaseCount(int status) { counter.increment("status." + status); if (!statusList.contains("counter.status." + status)) { statusList.add("counter.status." + status); } } }

7.3. Export Metrics Using MetricRepository

Next – we need to export the metrics – using the 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); } }
    

Note that we're storing counts of status codes per minute.

7.4. Spring Boot PublicMetrics

We can also use Spring Boot PublicMetrics to export metrics instead of using our own filters – as follows:

First, we have our scheduled task to export metrics per minute:

@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); }
    

We, of course, need to initialize the list of HTTP status codes:

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

And then we're going to actually update the metrics with status code count:

private void updateMetrics(Metric counterMetric, ArrayList statusCount) { 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); } }

Note that:

  • PublicMetics status counter name start with “counter.status” for example “counter.status.200.root
  • We keep record of status count per minute in our list statusMetricsByMinute

We can export our collected data to draw it in a graph – as follows:

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)))); } List minuteOfStatuses; List last = new ArrayList(); for (int i = 1; i < rowCount; i++) { minuteOfStatuses = statusMetricsByMinute.get(i - 1); for (j = 1; j = j ? last.get(j - 1) : 0); } while (j < colCount) { result[i][j] = 0; j++; } last = minuteOfStatuses; } return result; }

7.5. Draw Graph Using Metrics

Finally – let's represent these metrics via a 2 dimension array – so that we can then graph them:

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++; } ArrayList temp; 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; }

And here is our Controller method getMetricData():

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

And here is a sample response:

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

In Spring Boot 2, Spring Actuator's APIs witnessed a major change. Spring's own metrics have been replaced with Micrometer. So let's write the same metrics example above with Micrometer.

8.1. Replacing CounterService With MeterRegistry

As our Spring Boot application already depends on the Actuator starter, Micrometer is already auto-configured. We can inject MeterRegistry instead of CounterService. We can use different types of Meter to capture metrics. The Counter is one of the Meters:

@Autowired private MeterRegistry registry; private List 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. Exporting Counts Using MeterRegistry

In Micrometer, we can export the Counter values using 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); } else { statusCount.add(0); } } statusMetricsByMinute.add(statusCount); }

8.3. Publishing Metrics Using Meters

Now we can also publish Metrics using MeterRegistry's Meters:

@Scheduled(fixedDelay = 60000) private void exportMetrics() { ArrayList lastMinuteStatuses = 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. Conclusion

In this article, we explored a few simple ways to build out some basic metrics capabilities into a Spring web application.

Note that the counters aren't thread-safe – so they might not be exact without using something like atomic numbers. This was deliberate just because the delta should be small and 100% accuracy isn't the goal – rather, spotting trends early is.

Sudah tentu ada cara yang lebih matang untuk merakam metrik HTTP dalam aplikasi, tetapi ini adalah kaedah yang mudah, ringan dan sangat berguna untuk melakukannya tanpa kerumitan tambahan alat lengkap.

Pelaksanaan penuh artikel ini boleh didapati dalam projek GitHub.

REST bawah

Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:

>> SEMAK KURSUS