捕获和加强原生系统的可观测性来发现错误

做者:唐刘git

在对 TiDB 进行 Chaos 实践的时候,我一直在思考如何更好的发现 TiDB 整个系统的故障。最开始,咱们参考的就是 Chaos Engineering 里面的方式,观察系统的稳定状态,注入一个错误,而后看 metrics 上面有啥异常,这样等实际环境中出现相似的 metrics,咱们就知道发现了什么故障。github

但这套机制其实依赖于如何去注入错误,虽然如今咱们已经有了不少种错误注入的方式,但总有一些实际的状况咱们没有料到。因此后来咱们又考虑了另外的一种方式,也就是直接对 metrics 历史进行学习,若是某一段时间 metrics 出现了不正常的波动,那么咱们就能报警。但这个对咱们现阶段来讲难度仍是有点大,只使用了几种策略,对 QPS,Latency 这些进行了学习,并不能很好的定位到具体出了什么样的问题。算法

因此我一直在思考如何更好的去发现系统的故障。最近,恰好看到了 OSDI 2018 一篇 Paper,Capturing and Enhancing In Situ System Observability for Failure Detection,眼睛一亮,以为这种方式也是能够来实践的。跨域

你们都知道,在生产环境中,故障是无处不在,随时可能发生的,譬如硬件问题,软件自身的 bug,或者运维使用了一个错误的配置这些。虽然多数时候,咱们的系统都作了容错保护,但咱们仍是须要能尽快的发现故障,才好进行故障转移。网络

但现实世界并无那么美好,不少时候,故障并非很明显的,譬如整个进程挂掉,机器坏掉这些,它们处于一种时好时坏的状态,咱们一般称为「Gray Failure」,譬如磁盘变慢了,网络时不时丢包。这些故障都很是隐蔽,很难被发现。若是单纯的依赖外部的工具,其实很难检测出来。架构

上面是做者举的一个 ZooKeeper 的例子,client 已经彻底不能跟 Leader 进行交互了,可是 Leader 却仍然可以给 Follower 发送心跳,同时也能响应外面 Monitor 发过来的探活命令。运维

若是从外面的 Monitor 看来,这个 ZooKeeper 集群仍是正常的,但其实它已经有故障了。而这个故障其实 client 是知道的,因此故障检测的原理很简单,从发起请求的这一端来观察,若是发现有问题,那就是有故障了。而这也是这篇论文的中心思想。异步

在论文里面,做者认为,任何严重的 Gray Failure 都是可以被观察到的,若是发起请求的这边遇到了错误,天然下一件事情就是将这个错误给汇报出去,这样咱们就知道某个地方出现了故障。因而做者开发了 Panorama 这套系统,来对故障进行检测。socket

总体架构

先来讲说 Panorama 一些专业术语。分布式

Panorama 总体结构以下:

Panorama 经过一些方式,譬如静态分析代码进行代码注入等,将 Observer 跟要观察的 Subject 进行绑定,Observer 会将 Subject 的一些信息记录而且汇报给本地的一个 Local Observation Store(LOS)。本地一个决策引擎就会分析 LOS 里面的数据来判断这个组件的状态。若是多个 LOS 里面都有对某个 Subject 的 observation,那么 LOS 会相互交换,用来让中央的 verdict 更好的去判断这个 component 的状态。

故障断定

而用来判断一个 component 是否是有故障也比较容易,采用的是一种大多数 bounded-look-back 算法。对于一个 subject,它可能会有不少 observations,首先咱们会对这些 observations 按照 observer 进行分组,对每组单独进行分析。在每一个组里面,observations 会按照时间从后往前检查,而且按照 context 进行聚合。若是一个被观察的 observation 的 status 跟记录前面相同 context 的 observation status 状态不同,就继续 loop-back,直到遇到一个新的 status。对于一个 context,若是最后的状态是 unhealthy 或者 healthy 的状态没有达到多数,就会被认为是 unhealthy 的。

经过这种方式,咱们在每组里面获得了每一个 context 的状态,而后又会在多个组里面进行决策,也就是最经常使用的大多数原则,哪一个状态最多,那么这个 context 对应的状态就是哪个。这里咱们须要额外处理下 PENDING 这个状态,若是当前状态是 HEALTHY 而以前老的状态是 PENDING,那么 PENDING 就会变成 HEALTHY,而若是一直是 PENDING 状态并超过了某个阈值,就会退化成 UNHEALTHY。

Observability

这里再来讲说 Observability 的模式。对于分布式系统来讲,不一样 component 之间的交互并非同步的,咱们会面临以下几种状况:

若是两个组件 C1 和 C2 是同步交互,那么当 C1 给 C2 发送请求,咱们就彻底能在 C1 这一端知道此次请求成功仍是失败了,可是对于非同步的状况,咱们可能面临一个问题,就是 C1 给 C2 发了请求,但其实这个请求是放到了异步消息队列里面,但 C1 以为是成功了,但是后面的异步队列却失败了。因此 Panorama 须要有机制能正确处理上面多种状况。

为了能更好的从 component 上面获得有用的 observations,Panorama 会用一个离线工具对代码进行静态分析,发现一些关键的地方,注入钩子,这样就能去汇报 observations 了。

一般运行时错误是很是有用的能证实有故障的证据,可是,并非全部的错误都须要汇报,Panorama 仅仅会关系跨 component 边界产生的错误,由于这也是经过发起请求端能观察到的。Panorama 对于这种跨域的函数调用称为 observation boundaries。对于 Panorama 来讲,第一件事情就是定位 observation boundaries。一般有两种 boundaries,进程间交互和线程间交互。进程间交互一般就是 socket I/O,RPC,而线程间则是在一个进程里面跨越线程的调用。这些 Panorama 都须要分析出来。

当定位了 observation boundaries 以后,下一件事情就是肯定 observer 和 subject 的标识。譬如对于进程间交互的 boundaries,observer 的标识就多是这个进程在系统里面的惟一标识,而对于 subject,咱们能够用 method 名字,或者是函数的一个参数,类里面的一个字段来标识。

而后咱们须要去肯定 observation points,也就是观测点。一般这些点就是代码处理异常的地方,另外可能就是一些正常处理返回结果但会对外报错的地方。

上面就是一个简单分析代码获得 observation points 的例子,但这个仍然是同步的,对于 indirection 的,还须要额外处理。

对于异步请求,咱们知道,经过发出去以后,会异步的处理结果,因此这里分为了两步,叫作 ob-origin 和 ob-sink。以下:

对于 ob-origin,代码分析的时候会先给这个 observation 设置成 PENDING 状态,只有对应的 ob-sink 调用而且返回了正确的结果,才会设置成 HEALTHY。由于 ob-origin 和 ob-sink 是异步的,因此代码分析的时候会加上一个特殊的字段,包含 subject 的标识和 context,这样就能让 ob-origin 和 ob-sink 对应起来。

小结

上面大概介绍了 Panorama 的架构以及一些关键的知识点是如何实现的,简单来讲,就是在一些关键代码路径上面注入 hook,而后经过 hook 对外将相关的状态给汇报出去,在外面会有其余的分析程序对拿到的数据进行分析从而断定系统是否在正常工做。它其实跟加 metrics 很像,但 metrics 只能看出哪里出现了问题,对于想更细致定位具体的某一个问题以及它的上下文环境,倒不是特别的方便。这点来讲 Panorama 的价值仍是挺大的。

Panorama 的代码已经开源,总的来讲仍是挺简单的,但我没找到核心的代码分析,注入 hook 这些,有点遗憾。但理解了大概原理,其实先强制在代码写死也何尝不可。另外一个比较可行的办法就是进行在代码里面把日志添加详细,这样就不用代码注入了,而是在外面写一个程序来分析日志,其实 Panorama 代码里面提供了日志分析的功能,为 ZooKeeper 来设计的,但做者本身也说到,分析日志的效果比不上直接在代码里面进行注入。

那对咱们来讲,有啥能够参考的呢?首先固然是这一套故障检查的理念,既然 Panorama 已经作出来而且能发现故障量,天然咱们也能够在 TiDB 里面实施。由于咱们已经有在 Go 和 Rust 代码里面使用 fail 来进行错误注入的经验,因此早期手写监控代码也何尝不可,但也能够直接完善日志,提供一个程序来分析日志就成。若是你对这块感兴趣,想把 Panorama 相关的东西应用到 TiDB 中来,欢迎联系我 tl@pingcap.com。

相关文章
相关标签/搜索