必须编写服务,以便即使关键组件发生故障,也始终保持最小的功能。 Yandex.Taxi后端产品开发团队之一的负责人Ilya Sidorov在他的报告中解释说,当系统的某些部分不起作用时,我们如何让用户订购汽车,以及通过什么逻辑激活该服务的简化版本。
重要的是不仅要编写运行良好的服务,而且还要编写破坏良好的服务。
“很高兴见到大家。” 今天,我将谈论优雅降级。 如果您在Yandex中寻找它,那么很可能会学到如何使您的网站在不使用JS的情况下工作。 我会告诉你一些其他事情。 关于相对于后端的正常降级。

让我们从定义开始。 在现实中看起来像什么?

如果其中一项服务不起作用,我们将在此处显示我们的Yandex.Taxi应用程序-该服务用于选择驾驶员应带您前往的目的地。 如您所见,在此屏幕上没有大按钮“订购出租车”,这意味着用户将无法使用该服务。 但是您可以尝试降低性能,并允许用户不要选择B点。
然后,他将无法找到确切的行程价格,我们将无法建立路线,但是用户将拥有一个“订购出租车”按钮,他将可以使用我们的服务。 我们应用程序的主要功能将可用。 这就是我今天要谈的。 关于如何正确降级以及使用已损坏的服务可以做什么。
绩效计划。 我将告诉您如何降低服务质量。 您可以将其关闭,也可以使用其他行为。 然后,我将告诉您如何了解何时该关闭我们的服务。 最后,我将讨论在为Yandex.Taxi建立自动降级系统时必须面对的一些细微差别。
服务损坏了怎么办? 您可以关闭功能。 如果用于预测单个目的地的服务不起作用,则请关闭此服务。 如果驾驶员和乘客之间的聊天不起作用,则请关闭聊天。 如果您无法订购汽车,请关闭“订购汽车”按钮-哦,不,这不起作用。 并非所有功能都可以关闭。 而且,如果您无法关闭某些功能,则需要使用其他方法。 例如,您可以尝试制作布局或简化功能。 我们在Yandex中将这种简化的行为称为南瓜-我们说该服务已变成南瓜。
让我们更详细地考虑这些解决方案。

如何禁用服务? 您可能可以选择正确的架构。 假设我们有一项整体服务。 如果其中一部分失败,则整个服务都会中断。 但是,如果我们将服务分为几个部分,以便客户针对不同的请求使用不同的服务,它将变得更好。
这将如何处理示例? 有一项Yandex.Taxi服务,其中有两个主要功能:订购出租车和与驾驶员聊天。 只要我们有一个整体后端,如果与驾驶员的聊天失败,订购出租车的基本功能将受到影响。


您可以尝试做什么? 将整体服务分为两部分。 一部分负责订购出租车,另一部分负责与驾驶员沟通。
现在一切看起来都好多了。 如果与驾驶员的聊天中断,则其他所有内容都将继续正常运行。

如您所见,客户端使用不同的API,不同的请求来下订单并与驱动程序进行通信。
但实际上,似乎现在一切都不太好,因为聊天服务和订购服务之间存在虚假的联系。 事实证明,订购服务使用的是闲聊服务。 在这种情况下,主要功能将不起作用。

在这种情况下,一切都会好得多。 虚假的交流已经消失了,现在我们的服务真正彼此独立。 因此,如果聊天服务发生故障,您仍然可以乘坐出租车。
由此得出以下结论:如果要使用服务分离进行降级,那么确保服务彼此独立非常重要。 这意味着它们必须具有不同的入口点,不同的端点。 它们必须具有不同的运行时。 当然,他们必须使用不同的数据库。 否则,一项中断的服务可能会中断该链中的所有其他服务。

好了,我们已经找到了禁用功能的方法。 现在让我们看看如何制作默认功能,如何制作南瓜。 在此屏幕上,我们的目的地预测服务。 该服务使用智能AI预测当前用户的最佳目的地。 如果AI感到厌倦,那么我们将使用默认行为,并向用户提供离开莫斯科的权利。
让我们看看这在实践中是如何工作的。

我们有一个客户,他联系目标服务并收到错误消息。

现在有两种情况是可能的。 如果失败是单个失败,则第一种情况只是一个失败的请求。 在这种情况下,我们只是向客户抛出错误,他将重新请求并获得他最喜欢的目的地。
但是,如果故障很大,我们打开南瓜,用户将获得默认行为。

但是这种顽固的行为更容易实现,并且这个南瓜非常可靠,因此即使AI失败,它也可以使我们正常工作。 如果我们知道用户经常去机场旅行,那么我们将不会注意到用户生活的显着恶化。

即使降级模式已打开,南瓜也已打开,但是用户联系服务并收到成功的响应,则我们使用此答案,而不是南瓜。 这种行为-在答案的情况下我们使用它,在错误的情况下我们使用南瓜-我们称为后备模式。

没有错误-成功的回应。 有一个错误-南瓜。 我们说后备状态已经打开。
我理清了服务已损坏的情况。 您可以将其关闭,也可以打开南瓜。 现在让我们继续第二部分,看看如何进行诊断。
我们有两个大问题需要回答。 第一种是当您需要关闭服务并打开南瓜时。 第二种是您需要关闭南瓜并重新打开服务时。 在回答这些问题之前,我们需要澄清一点。

在与大量代理交互的任何复杂系统中,总会有一些错误背景。 在这张幻灯片上,我们看到了致电我们服务之一的真实时间表。 达到了数千RPS,我们得到的错误率不到1%。 这是对数刻度。
错误可能是由各种原因引起的。 也许这是某种内部过程,正在更新某种数据库或只是后台过程。 也许客户提出了错误的要求,但事实仍然存在:我们始终会有错误的背景。 让我们继续前进。

因此,我们使用基于统计的解决方案。 我们有一个特殊的数据库,用于保存统计信息,保存成功查询的数量,有错误的查询的数量以及包含回退的查询。 我们会在一段时间内通过滑动窗口收集并累积有关我们服务的统计信息。 当此滑动窗口中有错误的请求比例超过某个阈值时,我们启用后备。 当错误数量小于阈值时,我们将其关闭。
注意所选区域。 在19:01,开始出现第一个错误,但到目前为止,它们的份额很小,直到19:02为止,我们不包括回退。 在19:02超过了阈值,我们打开了回退。 在19:08的相反过程:错误结束了,但是有一段时间我们启用了后备功能,因为在我们的滑动窗口中仍然超过了阈值。 在19:09,我们关闭了后备广告。
我们确定了何时关闭该服务。 有必要回答第二个问题:何时打开它。 很简单:我们基于统计数据使用相同的解决方案。

重要的是,即使打开降级模式,也不要从服务中删除负载。 即使我们向用户显示南瓜,这也使我们能够继续接收统计信息。 因此,我们可以确定错误已结束,服务已修复。 因此,您可以将其重新打开。

当我们谈论退化时,我们不能说监视。 良好的监视是成功的一半,是自动关闭或自动降级的一半。 对于我们来说,重要的是要了解我们的服务会发生什么类型的问题,错误的性质以及错误发生的频率。 也许在第一阶段,我们甚至不需要断路器。 简单来说,如果监控灯亮起,我们可以手动打开和关闭服务。 监控灯熄灭后,我们将打开服务。
如果我们执行自动降级,自动切换,那么对回退本身进行监视非常重要。 如果降级系统运行良好,那么用户实际上可能根本不会注意到我们发生了什么故障。 如果没有监控,我们自己可以不注意到它。 监视回退很重要,了解回退的时间非常重要,以便了解统计信息,这样我们就可以了解统计信息,并且我们可以了解该功能不起作用的时间,后端随着时间的推移会变差还是变好,这取决于我们考虑花费多少时间进行回退。
一切都与主体有关。
最后,我想告诉您一些在Yandex.Taxi中开发自动降解系统时必须面对的细微差别。

您首先要注意的是一致性。 如果您要对某项服务进行自动降级,则该服务对其所有客户做出一致的响应非常重要。 如果您有两个使用该服务的客户端,则在降级的情况下这两个客户端的答案必须保持一致是很重要的。 而且,如果您的服务涉及到一些冗长的过程,则需要了解:也许在过程的开始和结束时,该服务将正常运行,并且将启用中间的某个备用功能。
听起来很复杂,但是让我们尝试用一个例子来解释。 也许它将变得更加清晰。

这是我们的司机和乘客之间的聊天。 降级的最简单方法是禁用它。 让我们想象一下,驾驶员的聊天记录已中断。 会发生什么? 客户端将写入聊天,但驱动程序将看不到消息。 他们可能会很不高兴,当他们见面时会对我们的应用发誓。 在这种情况下,对于聊天中的所有参与者,同时打开或同时关闭聊天是很重要的。 这就是我所说的一致性。

第二个细微差别涉及我们的Yandex.Taxi应用程序是地理分布的事实:可以在莫斯科,克拉斯诺亚尔斯克或赫尔辛基订购出租车。 即使在开发降级系统时也必须考虑到这一点。 想象一下,我们有很多成功的请求,很少有错误的请求。 看来这是正常情况,错误背景始终存在。 但是您可以以不同的方式看同一张图片。
您会看到该服务在Mytishchi中不起作用,您需要为这些用户启用回退。 结论是:您需要建立正确的统计数据。 对于我们来说,作为地理分布服务,这还意味着我们需要按城市构建统计信息。 如果我们正确地进行了统计,我们将立即看到Mytishchi的大多数请求都中断了,并专门为Mytishchi的用户启用了回退。 对于所有其他用户,我们将继续以正常模式工作,因为对于他们而言,该服务可以正常工作。

对于其他服务,也许会有不同的条件和其他细微差别。
我们的服务越来越复杂。 通常,它们取决于外界,这是我们无法预测的。 因此,不仅要编写运行良好的服务,而且还要编写破坏良好的服务,这一点很重要。 如果您学到新东西,请告诉同事,分享。 喜欢,分享,转发。 正确降级。