打造Java可观测性的5个关键步骤

作者:砧木 阿里云智能-基础产品事业部-块存储-平台服务研发

伴随云原生和微服务的普及,可观测性设计基本上是作为一个线上业务服务必备的基础能力。这篇文章我将介绍天罡项目围绕可观测性的三大支柱:日志,指标以及链路追踪所做的可观测性设计和实践,以及项目中实施可观测性的5个关键步骤。


本文中提供的例子,适用于Spring Boot 2.x版本


步骤一: 健康检查,还活着吗?

首先我们需要知道我们的应用实例是否还活着, 关于活着这件事情。 又可以细分为进程还在不在,如果进程在那应用实例是否能对外提供服务。 所以关于健康检查这件事情就可以分为:存活态(liveness)和就绪态(readiness)。就绪态又包含,应用自身服务的就绪以及必要依赖的外部组件的就绪状态。actuator内置了常见的如datasource, redis等常见外部依赖的依赖检查,对于未内置支持的外部依赖。可以通过自定义Healthlndicators的方式接入。

curl -Li http://localhost:8080/actuator/health
HTTP/1.1 200 
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Date: Fri, 05 Nov 2021 03:37:54 GMT

{"status":"UP","components":{"db":{"status":"UP","details":{"database":"MySQL","validationQuery":"isValid()"}},"diskSpace":{"status":"UP","details":{"total":494384795648,"free":372947439616,"threshold":10485760,"exists":true}},"livenessState":{"status":"UP"},"ping":{"status":"UP"},"readinessState":{"status":"UP"}}}%

通过健康检查接口,无论是容器还是非容器环境,可以配合负载均衡或者kubernetes的探针,都可以实现对不可用实例流量的自动隔离。抛开spring boot框架来说,健康检查接口应该能够支持开发者或这外部系统快速判断存活与就绪状态。 没有涵盖到这两部分的健康检查都是耍流氓。此外这些数据还可以通过HTTP采集插件采集到SLS来设置告警。

步骤二: 监控指标,活得还好吗?

其次,当应用满足了活着这个基本条件以后,我们就需要知道我们的应用到底活着怎么样, 好不好,有没有潜在风险。对人来说如何评估你的活的好不好,就的看你所处的社会大环境好不好,家里还有没有余粮,以及你自己身体的机能状态。对应用也一样,以Java为例,首先是应用所在环境主机或者容器的物理资源情况。CPU,内存,磁盘,网络这几大基础件,其次是JVM相关的运行指标,最后再是应用自身的业务相关指标。


对于主机监控,天罡使用了Logtail内置的主机监控能力,并存储到SLS的时序存储中。除了主机监控以外,JVM以及应用业务指标监控则采用了prometheus规范。通过集成micrometer,默认即可输出JVM的相关监控指标。而对于spring-boot不支持的监控指标,如druid的数据库连接池状态,http client连接池的状态则通过自定义Metrics的方式进行统一采集。而为了让各个服务有统一的监控指标体系,这些常用组件的metrics则通过spring-starter二方库的形式进行管理,对于已有服务和新的服务可以无缝集成统一的监控指标体系。而对于业务类型的指标,自定义Metrics的方式同样适用。

curl -Li http://localhost:8080/actuator/prometheus
HTTP/1.1 200
Content-Type: text/plain; version=0.0.4;charset=utf-8
Content-Length: 13244
Date: Fri, 05 Nov 2021 06:32:31 GMT

# HELP jdbc_connections_active Current number of active connections that have been allocated from the data source.
# TYPE jdbc_connections_active gauge
jdbc_connections_active{app="aliyun-center",name="dataSource",source="30.1.1.1",} 0.0
# HELP jvm_gc_max_data_size_bytes Max size of long-lived heap memory pool
# TYPE jvm_gc_max_data_size_bytes gauge
jvm_gc_max_data_size_bytes{app="aliyun-center",source="30.1.1.1",} 2.863661056E9
# HELP jdbc_connections_max Maximum number of active connections that can be allocated at the same time.
# TYPE jdbc_connections_max gauge
jdbc_connections_max{app="aliyun-center",name="dataSource",source="30.1.1.1",} 8.0
# HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process
# TYPE process_cpu_usage gauge
process_cpu_usage{app="aliyun-center",source="30.1.1.1",} 0.0012296864771536287
# HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time
# TYPE system_load_average_1m gauge
system_load_average_1m{app="aliyun-center",source="30.1.1.1",} 3.7958984375

而对于应用监控指标的采集和存储,新版的Logtail组件内置了prometheus监控指标的采集能力,操作参考通过Logtail插件接入Prometheus监控数据

步骤三: 链路追踪,有迹可循

不过即使注意了日常健康保养,小灾小病也不可避免,假如你有点不舒服,表现出来可能是脑袋疼,但是实际上可能是一连串连锁反应后的结果。 链路追踪就是为了让我们能够在不舒服的时候能够以最快的速度排出杂音,迅速缩小问题范围。


在天罡中,我们采用skywalking agent采集上报链路数据,具体安装和配置可以参考:通过SkyWalking上报Java应用数据
打造Java可观测性的5个关键步骤
skywalking会为每一个请求生成一个唯一的trace id. 通过trace id可以查看整个完整的调用链路,并且可以快速确定问题实际的产生位置。


通常来说云产品的所有调用都是通过POP API进行接入,用户反馈问题时通常会提供报错时反馈的RequestId信息。而如何通过RequestId快速查找调用链路,或者通过调用链路找到RequestId. 这就是接下来日志相关性所要解决的问题。

步骤四: 日志相关性,有理可循

最后我们的应用是一个时刻运行的系统,这个系统每时每秒都在以日志的形式记录着它的运行状态和运行时信息,当发现问题时,我们能够根据这些运行记录定位到根因,为了能够找到真正有用的信息。 这时就需要我们的日志具有相关性,能够关联到某个特定的事件或者流程,因此搜集日志是其一(通常是外部运维环境决定),能够找到相关性日志则是更重要的点。


首先是RequestId, 借用logback的MDC机制,我们只需要在适当的位置,在MDC中注入request id.即可确保当前请求所产生的日志具有固定的标识:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
    String requestId = request.getHeader(TBS_REQUEST_ID);
    if (!Strings.isNullOrEmpty(requestId)) {
        MDC.put("requestID", requestId);
    }
    return true;
}


而skywalking本身内置了与logback的集成,可以自动往MDC中注入traceId信息。引入依赖即可:

<dependency>
  <groupId>org.apache.skywalking</groupId>
  <artifactId>apm-toolkit-logback-1.x</artifactId>
  <version>8.5.0</version>
</dependency>


并在logback配置中,打印即可:

<property name="FILE_LOG_PATTERN"
              value="[%X{tid}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{requestID}] %logger{36}:%line [%thread] - %msg%n"/>
<!--省略的其-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
  <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
    <pattern>${LOG_PATTERN}</pattern>
  </layout>
</encoder>


通过日志相关性配置后,我们既可以通过主动发现慢请求链路,通过trace id找到相关的日志记录,进行主动的问题分析。 也可以在线上问题排查是根据RequestId找到调用链路,定界问题,并找到相关的日志进行分析。

链路关联日志

打造Java可观测性的5个关键步骤

日志关联链路


打造Java可观测性的5个关键步骤

步骤五: 可视化,看见变化

到目前为止,关于可观测性,我们在应用中内置了健康检查接口,prometheus的metrics接口以及skywalking agent。 同时通过日志相关性策略,将业务,链路进行关联,帮助我们快速定位和分析问题。 那可观测性最后的一步当然是构建我们的可视化Dashboard了。而SLS时序存储提供标准的Prometheus接口,因此我们可以直接将SLS时序存储作为Prometheus数据源加入到Grafana中,并通过 Grafana进行可视化。
打造Java可观测性的5个关键步骤

Dashboard本身也应该是持续迭代和演进的,因此本着万物皆可as code的原则,我们使用git来管理我们的可视化大盘。

.
├── README.md
├── docker-compose.yaml
└── grafana
    ├── config.monitoring
    ├── dashboards
    │   ├── app
    │   │   └── jvm-micrometer.json # jvm监控打包
    │   └── server
    │       └── hosts.json # 主机监控大盘
    └── provisioning # 初始化
        ├── dashboards
        │   └── dashboard.yaml # dashboard配置
        └── datasources
            └── datasource.yaml # 数据源配置


小结

可观测性,有如一把钥匙,让我们打开了应用的黑盒子,让我们可以时刻了解我们应用自身的运行情况。同时通过外在表现(监控指标)与内部状态(链路和日志)帮助我们更快的定位和分析问题。

上一篇:P4159 [SCOI2009] 迷路


下一篇:BZOJ 1025. [SCOI2009]游戏