在Uber,我们将实时系统监控与智能警报机制相结合,以确保应用程序的可用性和可靠性。为了让我们的工程师能够编写更准确的警报,Uber的可观察性应用团队试图引入警报val-能够确定一个给定的警报配置在过去是否已经关闭,以及何时关闭,从而更容易预测未来的警报。
我们团队构建这个功能的目标激励我们彻底改革我们的异常检测平台工作流程,重塑了工程师们既能要求新的预测,又能确保我们考虑到过去的时间的过程。由此产生的管道,围绕着预测工作的形式化概念,实现了预测的直观和性能回填,为更智能的警报铺平了道路。
监控和警报回测
为了保持我们服务的可靠性,我们的随叫随到的工程师需要在出现停机或其他异常行为时尽快得到通知和警报。Uber Engineering的警报授权和管理平台uMonitor允许工程师根据服务的关键指标定义警报,从而实现了这种监督。
在其核心部分,uMonitor警报由一个指标查询、一组警报阈值和一组在被监视的指标超过相应阈值时要执行的操作。这些行为可以被认为是钩子例如,这可能允许有问题的警报用一个推送通知呼叫负责该服务的团队,对于极端的度量偏差,而对于相对较小的偏差,只发送一个非侵入性的电子邮件。在这些基线属性之上,uMonitor还为作者提供了一套健壮的配置选项,以更微妙的方式进一步支持警报行为——例如,通过异常检测根据动态阈值(而不是传统的静态阈值)发出警报,或者在阈值违反持续一定时间之前抑制警报。
由于这种灵活性,Uber的警报由无数自由浮动的参数组成,所有这些参数都由警报作者控制。事实上,uMonitor是如此的可定制,以至于Observability Applications团队甚至使用它来监视自身(当然是通过一个单独的实例)。例如,该团队配置了一个警报,每当全局警报执行陷入停顿时,该警报就会对其待命工程师进行分页——这表明系统本身可能出现了问题。
然而,这种灵活性也带来了复杂性;对于一些团队来说,在uMonitor上管理、维护和完善他们的警报可能成为一个与他们所监视的服务一样复杂的项目。此外,异常检测等功能虽然对某些类型的警报至关重要,但却为警报引入了一定程度的不透明,工程师可能难以接受或信任。因此,有时工程师很难预测给定警报可能触发的情况或确定配置的最优性;有时,针对次优警报的解决方案可能就像隐藏在显眼处的配置更改一样简单。
考虑到这一挑战,我们试图实现alertval在uMonitor。该特性允许工程师“试运行”给定的警报配置,以可视化它将如何响应最近的、历史的生产行为,支持响应反馈循环,通过该循环,工程师可以逐步微调他或她的警报,直到最终保存它。我们相信,警报回测将有助于消除警报授权过程中固有的一些不透明和脱节,最终允许工程师为Uber的服务定义更准确和可操作的警报。
孤立的低效率
由于uMonitor的异常检测功能依赖于独立的预测层F3生成的动态阈值,我们无法简单地向用户推出该功能。我们意识到,支撑F3当前工作流程的一系列假设,使我们无法向所有uMonitor用户发布警报回测,而不会给底层指标系统(F3支持和支持的存储和查询生态系统)带来沉重负担。
自成立在2015年末,F3提供了专属的迭代工作流程。相对于特定的时间点,消费者可以要求对近期的未来进行新的预测。按照设计,F3的唯一责任是逐个独立地产生新的预测。
另一方面,利用异常检测的回测警报需要能够保证给定时间范围的预测完全覆盖任何给定的时间序列度量。理想的系统是将历史预测补上,以弥补任何子区间的缺失。这样的功能会有有用的应用。例如,考虑一个场景,其中工程师想确定异常检测是否有助于提高警报的信噪比。这些警报从未进行异常检测,因此在预测存储中没有预先存在的历史预测可供比较。有了专门的回填API,工程师可以简单地获取他们想要的时间范围,并请求创建那些历史预测,立即提供基线预测值进行比较。
不可否认,一个专门的预测回填API并不是严格必要的,因为消费者可以简单地查询预测存储,它将为他们提供所需的所有信息,以计算缺少预测的时间范围,并将相应的请求发送到F3。事实上,我们使用这种方法实现了uMonitor回测功能的初步版本。然而,这种方法在F3中发现了一个更根本的低效率问题,只要预测请求是单独处理的,就不可能解决这个问题。
这种低效率与F3与基础指标系统的关系有关。对于每个预测请求,F3需要一组特定的历史数据用于相应的时间序列度量;这些数据构成了F3后续预测计算的基础。从历史上看,F3在每次预测操作期间查询度量系统的历史数据窗口—即使这些操作的数据需求有很大的重叠。在这种简单的实现下,我们的团队注意到,渐进地说,大约90%的请求数据都是冗余的。
这种冗余在度量系统上代表了大量的额外开销,我们的团队认为这是不可接受的。我们得出的结论是,如果我们不解决这些潜在问题就向uMonitor的用户发起警报回测,我们就会在指标查询服务上引入间歇性和完全任意的负载上升峰值的风险。如果查询服务在某个时间点无法解释这样的峰值,F3就有可能使其完全崩溃,压倒它想要支持和加强的生态系统。
总体优化
因此,我们团队在uMonitor中实现通用警报回测的努力要求我们完全重新处理F3的预测工作流程。我们需要的设计不仅支持服务的规范的、迭代的工作流程,而且还支持独立的工作流程大部分回填机制。
我们注意到,F3的基本操作是对在有限时间范围内有效的时间序列指标进行预测。换句话说,虽然该服务能够单独执行独立的预测操作,但它缺少一个抽象层,使其能够集体处理这些操作。
考虑到这个需求,我们决定正式地将服务引入预测的概念工作.这种对F3基本操作的新的形式化抽象不仅使服务能够执行这些操作,还能围绕它们进行优化。通过具体概述唯一定义和标识原子预测操作的属性—属性如查询—字符串,对应于预测下的时间序列度量、覆盖的时间范围以及具体的模型预测值对应的服务现在可以对这些作业进行合计计算。这个新功能为几个关键优化铺平了道路,这些优化共同为预测回填提供了更性能、更有效和更直观的API。
例如,该作业框架授权服务本地同步和协调预测操作。通过跟踪哪些作业已经启动、完成或当前正在进行中——无论是在内存缓存中还是在专用的数据库层中——f3可以确定任何入站请求与之前发出的另一个请求或当前正在处理的另一个请求相比是否冗余。
同样的机制还允许服务在更高的抽象级别上操作,比以前的用例更具交互性。例如,考虑这样一种情况,F3的消费者希望保证某个指标在整个时间范围内具有完全的预测覆盖率。通过了解过去已经创建了哪些预测,以及目前正在创建哪些预测,F3现在可以有效地确定哪些子范围缺少预测,并通过适当的工作数量和规范来填补空缺。
如前所述,该作业框架允许F3对预测操作进行总体操作。通过启用数据池,该功能可以立即获得操作收益。例如,考虑一个给定的预测请求导致多个预测作业,每个作业都有自己的历史数据需求的情况。如果这些作业覆盖的时间范围是相邻的或接近相邻的,那么孤立地满足每个作业的数据需求会导致网络流量和查询负载的严重冗余。作为预测操作抽象的作业使我们能够,例如,将每个作业的数据需求整合到一组查询窗口中,如图4所示。然后,可以将这个合并的数据请求一次性发送到查询服务,减少大量不必要的负载,同时仍然确保每个预测作业获得所需的数据。
把它们结合在一起:新的工作流程
作业数据类型的引入让我们可以极大地概括F3的预测管道,从而本机有效地支持新的回填用例。虽然迭代工作流在很大程度上保持不变,但对F3底层工作流的优化是重要的。
现在,有了以前和当前执行的所有预测作业的历史日志,F3可以为uMonitor和其他使用者提供一个简单、直观的API,该API只要求在给定的时间序列中覆盖他们希望覆盖的时间范围。然后,F3可以简单地将这段时间划分为必要的工作,并根据已经考虑到的子范围(无论是先前的预测还是正在进行的预测)进行划分。有了这组作业,服务现在可以计算最小的综合历史数据集,以满足其所有需求。
通过向度量系统发出针对该数据的单个请求,服务就可以根据需要对数据进行分区,只使用它们所需的子集片处理每个作业。最后,可以将所有作业的完成记录记录到F3的历史作业日志中,有效地向所有未来预测请求传递信息,即对给定的时间范围进行了充分考虑,不需要进一步处理。在F3的生产部署中新引入的数据池功能使我们能够将底层时间序列度量存储上的服务负担减少90%。
换句话说,在系统中引入一个简单的数据结构是一套优化和衍生特性的关键,它们共同导致的不仅是更直观的服务级API,而且还提高了可靠性和效率。
外卖
尽管该项目在时间序列预测和异常检测方面存在缺陷,但它最终还是归结为API设计的问题。uMonitor需要在不损害可观察性生态系统的其他关键组件的情况下,支持使用异常检测的警报的回测,这迫使我们完全重新设计F3的工作流程,这引发了一些问题,如:用户应该负责什么样的信息或细节?同样地,有了这些假设,从他们的角度来看,什么样的界面是最有意义的?
这种设计方法对于构建性能和直观的解决方案至关重要。通过确定理想的最终状态,而不考虑当前的事务状态,并反向工作,我们能够精确地指出服务当前状态中所缺少的功能,从而实现我们想要的行为。那些缺失的特性可以归结为一个单一的数据结构——它形式化了服务的基本操作,因此允许服务基于它进行优化。该数据结构,以及基于其核心思想的后续优化,使我们能够为F3实现显著的可伸缩性。作为这个项目的结果,F3和我们的可观察性异常检测平台处于一个更好的位置,以解决我们的工程师和整个Uber平台日益复杂的警报和监控需求。
如果你想让优步的工程师监督他们的生产系统可观测性的应用程序团队想和你谈谈。
非常感谢可观察性应用团队的Shreyas Srivatsan为这个项目提供建议。






