Java SpringBoot
现在,经常会使用Spring Boot以开发Web服务,其内嵌容器的方法的确使得开发效率大大提升。
由于网关层通常是直接面对用户请求的一层,也是微服务里面最上游的一个服务,其请求量通常是所有服务中最大的,如果服务出现了性能上的问题,网关层通常都会出现阻塞、超时等现象,这时候就很可能需要性能的调优,其中最常见的则是参数调优。但如何知道哪些性能参数成为了瓶颈(如容器线程数是否不足等),则是调优的前提条件。
在使用了Spring Boot的前提下,获取运行时的Tomcat性能运行情况。
Spring Boot中有一个Spring Boot actuator的模块,用来监控和管理应用的运行状态,例如健康状况,线程运行情况等。
Maven 依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-actuator</artifactId>
  5. </dependency>
  6. </dependencies>

然后当Spring Boot运行之后,Spring Boot会有很多服务暴露在http服务中,这些服务叫EndPoints, 通过 http://{应用路径}/actuator 这个 url 即可访问,例如 http://{应用路径}/actuator/infohttp://{应用路径}/actuator/health 这两个endpoints是默认开启的。
其中actuator这个路径可以通过配置修改:

  1. management.endpoints.web.base-path=/mypath

以下是获取健康状态的一个例子:

  1. $ curl 'http://localhost:8080/actuator/health' -i -X GET

可能会得到类似这样的结果:

  1. {
  2. "status" : "UP"
  3. }

比较简陋,如果希望这个接口有更多数据,可以尝试这样的配置:

  1. management.endpoint.health.show-details=always

结果就会丰富了(这里应用用了Redis):类似

  1. {
  2. "status": "UP",
  3. "details": {
  4. "diskSpace": {
  5. "status": "UP",
  6. "details": {
  7. "total": 214745214976,
  8. "free": 174805827584,
  9. "threshold": 10485760
  10. }
  11. },
  12. "redis": {
  13. "status": "UP",
  14. "details": {
  15. "cluster_size": 3,
  16. "slots_up": 16384,
  17. "slots_fail": 0
  18. }
  19. }
  20. }
  21. }

但是这还不够,需要详细的容器数据。监控状况只是一部分。而这些想要的数据,是在一个叫metric的EndPoint下面。但是此endpoint 默认没有暴露到http接口的的,需要添加配置:

  1. #默认只开启info, health 的http暴露,在此增加metric endpoint
  2. management.endpoints.web.exposure.include=info, health,metric

之后就能访问这个metric有哪些数据了

  1. $ curl 'http://localhost:8080/actuator/metric' -i -X GET
  2. {
  3. "names": [
  4. "jvm.memory.max",
  5. "jvm.threads.states",
  6. "process.files.max",
  7. "jvm.gc.memory.promoted",
  8. "tomcat.cache.hit",
  9. "tomcat.servlet.error",
  10. "system.load.average.1m",
  11. "tomcat.cache.access",
  12. "jvm.memory.used",
  13. "jvm.gc.max.data.size",
  14. "jvm.gc.pause",
  15. "jvm.memory.committed",
  16. "system.cpu.count",
  17. "logback.events",
  18. "tomcat.global.sent",
  19. "jvm.buffer.memory.used",
  20. "tomcat.sessions.created",
  21. "jvm.threads.daemon",
  22. "system.cpu.usage",
  23. "jvm.gc.memory.allocated",
  24. "tomcat.global.request.max",
  25. "tomcat.global.request",
  26. "tomcat.sessions.expired",
  27. "jvm.threads.live",
  28. "jvm.threads.peak",
  29. "tomcat.global.received",
  30. "process.uptime",
  31. "http.client.requests",
  32. "tomcat.sessions.rejected",
  33. "process.cpu.usage",
  34. "tomcat.threads.config.max",
  35. "jvm.classes.loaded",
  36. "http.server.requests",
  37. "jvm.classes.unloaded",
  38. "tomcat.global.error",
  39. "tomcat.sessions.active.current",
  40. "tomcat.sessions.alive.max",
  41. "jvm.gc.live.data.size",
  42. "tomcat.servlet.request.max",
  43. "tomcat.threads.current",
  44. "tomcat.servlet.request",
  45. "process.files.open",
  46. "jvm.buffer.count",
  47. "jvm.buffer.total.capacity",
  48. "tomcat.sessions.active.max",
  49. "tomcat.threads.busy",
  50. "process.start.time"
  51. ]
  52. }

其中列出的是所有可以获取的监控数据,在其中发现了需要的

  1. "tomcat.threads.config.max"
  2. "tomcat.threads.current"
  3. "tomcat.threads.busy"

那么如何获取其中的值呢?只需要在metric路径下加上希望获取的指标即可:curl 'http://localhost:8080/actuator/metric/tomcat.threads.busy'

  1. {
  2. "name": "tomcat.threads.busy",
  3. "description": null,
  4. "baseUnit": "threads",
  5. "measurements": [{
  6. "statistic": "VALUE",
  7. "value": 1.0
  8. }],
  9. "availableTags": [{
  10. "tag": "name",
  11. "values": ["http-nio-10610"]
  12. }]
  13. }

在此,基本想要的数据都能实时的通过http服务接口的方式获取了,那么在流量峰值的时候,一些实时的状态便可获取到了。

监控数据

但是面对的情况是这样的,半个小时前,一个push活动带来了很大的量,但现在流量已经过去了,需要定位当时的性能问题意味着需要采集到过去的数据。所以可能需要一个服务定期去监控这些数据。
Spring Boot已经考虑到了这种情况,所以其中有一个prometheus的模块,他是一个独立的服务去采集其中的监控数据并可视化,具体的介绍可以参考:
https://www.callicoder.com/spring-boot-actuator-metrics-monitoring-dashboard-prometheus-grafana/

以日志形式定期输出监控数据

很多时候,如果有日志的方法去定期输出监控的数据这样已经足够分析了。在Spring Boot 2.x里,只需要配置一个Bean

  1. @Configuration
  2. class MetricsConfig {
  3. @Bean
  4. LoggingMeterRegistry loggingMeterRegistry() {
  5. return new LoggingMeterRegistry();
  6. }
  7. }

之所以需要Spring Boot版本2.x,LoggingMeterRegistry是因为是micrometer-core里面的1.10以上才引入的,而Spring Boot 1.x都低于这个版本,如果不想升级Spring Boot版本,可以尝试显示变更此版本:

  1. <dependency>
  2. <groupId>io.micrometer</groupId>
  3. <artifactId>micrometer-core</artifactId>
  4. <version>1.1.3</version>
  5. </dependency>

最后日志的内容就会每一分钟的打印出来:

  1. jvm.buffer.count{id=direct} value=26 buffers
  2. jvm.buffer.count{id=mapped} value=0 buffers
  3. jvm.buffer.memory.used{id=direct} value=632.600586 KiB
  4. jvm.buffer.memory.used{id=mapped} value=0 B
  5. jvm.buffer.total.capacity{id=direct} value=632.599609 KiB
  6. jvm.buffer.total.capacity{id=mapped} value=0 B
  7. jvm.classes.loaded{} value=12306 classes
  8. jvm.gc.live.data.size{} value=39.339607 MiB
  9. jvm.gc.max.data.size{} value=2.666992 GiB
  10. jvm.memory.committed{area=nonheap,id=Compressed Class Space} value=8.539062 MiB
  11. jvm.memory.committed{area=nonheap,id=Code Cache} value=26.8125 MiB
  12. jvm.memory.committed{area=heap,id=PS Survivor Space} value=30 MiB
  13. jvm.memory.committed{area=heap,id=PS Eden Space} value=416.5 MiB
  14. jvm.memory.committed{area=heap,id=PS Old Gen} value=242 MiB
  15. jvm.memory.committed{area=nonheap,id=Metaspace} value=66.773438 MiB
  16. jvm.memory.max{area=heap,id=PS Survivor Space} value=30 MiB
  17. jvm.memory.max{area=heap,id=PS Eden Space} value=1.272949 GiB
  18. jvm.memory.max{area=heap,id=PS Old Gen} value=2.666992 GiB
  19. jvm.memory.max{area=nonheap,id=Metaspace} value=-1 B
  20. jvm.memory.max{area=nonheap,id=Compressed Class Space} value=1 GiB
  21. jvm.memory.max{area=nonheap,id=Code Cache} value=240 MiB
  22. jvm.memory.used{area=nonheap,id=Code Cache} value=26.635071 MiB
  23. jvm.memory.used{area=heap,id=PS Survivor Space} value=25.214882 MiB
  24. jvm.memory.used{area=heap,id=PS Eden Space} value=46.910545 MiB
  25. jvm.memory.used{area=heap,id=PS Old Gen} value=39.34742 MiB
  26. jvm.memory.used{area=nonheap,id=Metaspace} value=63.333778 MiB
  27. jvm.memory.used{area=nonheap,id=Compressed Class Space} value=7.947166 MiB
  28. jvm.threads.daemon{} value=52 threads
  29. jvm.threads.live{} value=54 threads
  30. jvm.threads.peak{} value=67 threads
  31. jvm.threads.states{state=terminated} value=0 threads
  32. jvm.threads.states{state=blocked} value=0 threads
  33. jvm.threads.states{state=new} value=0 threads
  34. jvm.threads.states{state=runnable} value=20 threads
  35. jvm.threads.states{state=waiting} value=19 threads
  36. jvm.threads.states{state=timed-waiting} value=15 threads
  37. process.cpu.usage{} value=-1
  38. process.start.time{} value=435900h 48m 53.344s
  39. process.uptime{} value=56m 6.709s
  40. system.cpu.count{} value=8
  41. system.cpu.usage{} value=-1
  42. tomcat.global.request.max{name=http-nio-10610} value=0.597s
  43. tomcat.servlet.request.max{name=dispatcherServlet} value=0.567s
  44. tomcat.sessions.active.current{} value=0 sessions
  45. tomcat.sessions.active.max{} value=0 sessions
  46. tomcat.threads.busy{name=http-nio-10610} value=0 threads
  47. tomcat.threads.config.max{name=http-nio-10610} value=200 threads
  48. tomcat.threads.current{name=http-nio-10610} value=10 threads

如果需要修改打印的频率,可修改LoggingRegistryConfig以更改其打印频率

  1. //下面是单独的配置实现的参考,当需要修改配置时候可以使用
  2. return new LoggingMeterRegistry(new LoggingRegistryConfig() {
  3. @Override
  4. public Duration step() {
  5. return Duration.ofSeconds(10);//10秒输出一次
  6. }
  7. @Override
  8. public String get(String key) {
  9. return null;
  10. }
  11. }, Clock.SYSTEM);
  12. }