Saya baru sahaja mengumumkan kursus Learn Spring yang baru , yang berfokus pada asas-asas Spring 5 dan Spring Boot 2:
>> SEMAK KURSUS1. 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