可观察性(Observability)主要是指理解程序内部运行的能力。我们不想在应用程序发布和启动后对应用程序的内部结构一无所知。对我们来说,整个应用程序就是一个黑盒子。即使应用程序出现故障或崩溃,我们也可以获取到崩溃前的所有相关数据,这是飞机黑匣子(FlightRecorder)的设计出发点,如图1所示。图1Logging,Metrics,andFlightRecorder的Tracing目前,可观测性的架构设计主要涉及三个部分:Logging、Metrics和Tracing。下面从这三个方面详细介绍可观察性架构的设计。Aliware了解系统运行情况最简单的方法就是查看日志。为此,我们创建了很多日志框架、工具和系统,比如日志文件打印、日志文件收集工具、日志分析系统等,但是在实际运维中,我们不可能把所有的信息都详细记录下来,这样做是没有意义的。我们需要为日志设置不同的级别,比如debug、error、info等,在开发、测试、生产等不同的环境开启不同的日志级别,并保证这些日志级别可以在发生故障时实时调整。系统正在运行。通常,我们不需要考虑日志处理的问题。毕竟日志处理技术已经发展了很长时间,现在已经很成熟了。几乎所有的编程语言都有对应的日志框架。目前云厂商基本都提供日志服务,对接非常简单,也可以自己安装成熟的日志处理系统,比如ElasticStack等。MetricsAliware指标不仅包括CPU负载、内存占用等技术指标指标,还包括很多业务指标(BusinessMetrics),比如每分钟交易量、每分钟会员登录数等。对于这些业务度量参数,我们在做架构设计的时候,需要把它们都以参考指标的形式列出来,方便上线后观察数据,做出相应的业务决策。这里可能有读者有疑问。我们已经用日志记录了相关的数据,最终的数据也存储在了数据库中。为什么我们需要向数据中添加记录?要回答这个问题,我们先来看看以下的区别。首先,日志记录了某个时间点发生的事情,其中??包含了很多细节,可以说是无所不在。第二,数据库记录当前数据的最新快照。我们通常不会关注中间过程。例如,电子商务网站上某件商品的价格可能经过多次调整,但数据库通常只记录该商品的最新价格。第三,计量统计是一个窗口期的汇总数据,可以是平均值,也可以是累计值。如果是CPU负载,统计一段时间内的平均值;如果是1分钟内的成交单数,则需要统计累计值。还有一类比较特殊,就是那些没有时间间隔的情况,比如计数器等,它的值会在应用启动后的整个运行周期内不断累加,应用启动后会重新计算重新启动。虽然日志可以计算一些数据,比如订单数量、订单金额等,但是需要考虑数据分析的成本和实时性,才能更好地实现计算资源、存储节省和快速查询。测量和统计的是窗口期的数据,不需要重新计算,节省了计算资源;同时,不需要保存窗口期的每一个具体数据,可以节省存储资源;改进了窗口期的预处理,查询响应速度会更快。总的来说,指标部分处理可观察性数据中的垂直场景。当我们更关注某个窗口期的聚合数据,关注点主要是数据的趋势和对比时,度量正好可以满足这种需求。一个典型的metric主要由以下五部分组成:1)名称:因为metric的名称要表达其所代表的含义,所以最好使用命名空间级联的方式,可以用“.”分隔。比如域名,或者使用Prometheus中使用的“_”分隔符。2)时间点:收集指标的时间点通常由测量框架自动设置。3)数值:测量值只能是数值,不能是字符串等其他值。4)类型:典型的类型有计数器、直方图、平均比率、定时器、仪表等。5)标签:主要包括一些元信息,如源服务器ID、应用名称、组信息、运行环境等。标签是方便后续的metric查询和重新聚合处理。将以上信息保存到Prometheus等测量系统后,我们可以根据以上结构进行查询。PromQL是Prometheus提供的指标查询语言。最后,我将介绍一些基于测量系统的预警规则。告警规则非常丰富,这里有几条供大家参考:1)阈值告警:当某个指标的值低于或高于预设值时,就会触发告警。例如,CPU负载、业务指标降为零,这些都会触发警报。2)同期数据对比:在某些场景下,无法通过绝对值判断发现系统问题。比如一个电商网站在一天中不同时间的交易量是不一样的,所以比较每周同一天同一天同一时间的数据来判断问题会更加准确。3)趋势预警:主要是针对计数器类型的预警设置。如果测量值有急剧上升或下降,或超出正常曲线趋势,则需要引起我们的注意。回到实际的应用开发上,大部分云厂商也提供测量集成服务,比如阿里云的Prometheus服务。在程序中,我们基本上只需要直接连接即可,比如metrics的采集、存储、监控、告警、图表展示等数据监控服务。跟踪了Aliware的微服务架构,基本上是分布式架构设计。一个简单的HTTP请求可能涉及5个以上的应用程序。一旦出现问题,将很难快速定位。比如有用户反映会员登录很慢,基本都在5秒以上。这种情况下如何定位问题?定位问题涉及登录Web应用、账号验证服务、会员信息服务、登录安全监控系统,还涉及Redis、数据库等,没有高效的跟踪系统,排查定位问题的复杂程度可想而知。首先,让我们看一下跟踪系统的基本要素。1.traceIdtraceId用于标识一条trace链,比如64位或者128位的字符串。不同trace链的traceId是不同的。但在跟踪链中,traceId始终保持不变。traceId通常在请求进入时生成。比如对于HTTP请求,traceId基本是在网关层生成的,也可以延迟到具体的web应用。在生产环境中,并非所有请求都启用了跟踪。我们只会对部分请求进行抽样,例如,我们只会跟踪2%的请求。这主要是因为跟踪会给整个系统带来额外的开销。当然,在测试环境中,为了方便排查问题,建议对所有请求都开启tracing。2.spanIdspanId用于记录trace链中跨越时间段的操作。例如,访问数据库或进行RPC调用的过程对应一个spanId。在一个span中,ID的作用就是方便识别。ID通常是64位长值。名称是为了方便用户了解操作是什么,开始时间和结束时间是为了方便用户了解操作时长。此外,间隔还可以包含其他元信息。通常,一个跟踪链由多个区间组成。间隔提供特定的操作信息。区间的产生会涉及到应用中的代码,我们称之为区间埋点。3.parentId在跟踪链中,我们可能需要对一些区间进行分组,比如对一个应用内的多个区间进行分组,这样我们就可以了解应用在整个调用链中所花费的时间。解决方法是在interval中加入parentId,将不同类别的interval组合在一起。通常,我们会在进入应用时设置parentId。比如进入会员登录应用时会设置一个parentId,进入账户验证服务时会设置一个parentId,这样我们就可以根据不同的应用进行区间分类。在同一个应用程序内部,我们还可以根据应用程序的parentId设置一个子parentId。如果要对数据库相关的操作进行分类,列出数据库parentId下的所有操作。跟踪链可以连接整个请求在不同应用和系统中的操作信息。我们只要输入traceId,就可以了解到跟踪系统中整个调用链的详细信息。那么,在不同的应用和系统中,路由和路段信息是如何采集的呢?Zipkin是知名的路径跟踪产品,BraveSDK可以实现路径和区间信息的采集。BraveSDK负责创建路径和区间,并将这些信息异步上报给Zipkin,完成追踪链的数据采集。由于路径和路段信息的采集是通过远程调用实现的,采集过程必须异步实现。只有这样,才能保证正常的业务运营不受影响。最典型的采集方式是连接异步协议或系统,如gRPC、Kafka、RSocket等,保证所有的数据采集都是异步的。EventStreamSubscriptionAliwareLogging、Metrics、PathTracing是实现可观察性架构模式的三大保障。但在某些场景下,还有其他非常优雅的设计,例如JavaFlightRecorder(JFR)。与前面三者不同的是,这是基于事件流(EventStream)的推送设计。我们可以在应用程序中定义各种JFR事件,并在业务流程中触发这些事件。与日志记录不同,JFR事件可能不需要像CPU负载那样持久化记录保存到日志文件中,而是在用户对这些事件感兴趣时通过订阅的方式开始收集这些事件,我们暂且称之为事件流订阅。与日志分析相比,这种方法更加灵活。可随时启动、随时分析、随时退出,完全实时。基于JFR实现实时事件流订阅的好处是我们不需要关心额外的开销对系统性能的影响,因为JFR设计对系统开销的影响已经降到了很低的水平,小于1%,也就是日志对系统的影响小于1%。影响就更小了。这意味着在生产环境中,我们可以随时快速启动事件流监控。在Java14中,JFR得到了进一步的完善和完善,包括性能优化、自定义事件API和流式订阅等,使得JFR的使用更加简单。在最新的JDK15中,JFR事件类型数量高达157种,如CPU负载(jdk.CPULoad)、线程启动(jdk.ThreadStart)、文件读取(jdk.FileRead)、Socket读取(jdk.SocketRead),ETC。。这些都有事件记录,对监控很有帮助。但JFR仅适用于Java平台。如果一个项目是基于Java的,那么JFR可以很好的提高系统的可观察性。最新的JUnit5.7版本也默认支持JFR特性。
