Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

你好,我叫叶夫根尼。 我在 Yandex.Market 搜索基础设施中工作。 我想告诉哈布尔社区关于市场内部厨房的信息 - 我有很多话要说。 首先,市场搜索的工作原理、流程和架构。 我们如何处理紧急情况:如果一台服务器出现故障怎么办? 如果有 100 个这样的服务器怎么办?

您还将了解我们如何同时在一堆服务器上实现新功能。 以及我们如何直接在生产中测试复杂的服务,而不会给用户造成任何不便。 总的来说,市场搜索如何运作,让每个人都玩得开心。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

关于我们的一些信息:我们解决什么问题

当您输入文本、按参数搜索产品或比较不同商店的价格时,所有请求都会发送到搜索服务。 搜索是市场上最大的服务。

我们处理所有搜索请求:来自 market.yandex.ru、beru.ru、Supercheck 服务、Yandex.Advisor、移动应用程序等网站。 我们还在 yandex.ru 的搜索结果中包含产品报价。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

我所说的搜索服务不仅指搜索本身,还指包含市场上所有报价的数据库。 规模是这样的:每天处理超过十亿个搜索请求。 一切都应该快速进行,不间断,并始终产生预期的结果。

什么是:市场架构

我将简要描述市场当前的架构。 可以用下图来大致描述:
Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么
假设有一家合作伙伴商店来找我们。 他说我想卖一个玩具:这只邪恶的猫,会发出吱吱声。 还有另一只愤怒的猫,没有吱吱声。 而且只是一只猫。 然后商店需要准备市场搜索的报价。 商店生成包含优惠的特殊 xml,并通过联属网络接口传达此 xml 的路径。 然后,索引器定期下载此 xml,检查错误并将所有信息保存到一个巨大的数据库中。

这样保存的xml有很多。 从此数据库创建搜索索引。 索引以内部格式存储。 创建索引后,布局服务将其上传到搜索服务器。

结果,数据库中出现了一只发出吱吱声的愤怒的猫,并且该猫的索引出现在服务器上。

我会在搜索架构部分告诉你我们如何搜索一只猫。

市场搜索架构

我们生活在一个微服务的世界:每个传入的请求 market.yandex.ru 会产生大量的子查询,并且涉及到数十个服务的处理。 图中仅显示了一些:

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么
简化的请求处理方案

每个服务都有一个奇妙的东西 - 它自己的平衡器具有唯一的名称:

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

平衡器为我们管理服务提供了更大的灵活性:例如,您可以关闭服务器,这通常是更新所必需的。 平衡器发现服务器不可用,会自动将请求重定向到其他服务器或数据中心。 添加或删除服务器时,负载会自动在服务器之间重新分配。

平衡器的唯一名称不依赖于数据中心。 当服务 A 向 B 发出请求时,默认情况下均衡器 B 会将请求重定向到当前数据中心。 如果该服务不可用或当前数据中心不存在,则请求将被重定向到其他数据中心。

所有数据中心的单一 FQDN 允许服务 A 完全从位置中抽象出来。 他对服务 B 的请求将始终得到处理。 例外情况是服务位于所有数据中心的情况。

但这个平衡器并不是一切都那么美好:我们还有一个额外的中间组件。 平衡器可能不稳定,这个问题可以通过冗余服务器来解决。 服务 A 和 B 之间还存在额外的延迟。但实际上,该延迟小于 1 毫秒,对于大多数服务而言,这并不重要。

处理意外情况:搜索服务平衡和弹性

想象一下崩溃:你需要找到一只发出吱吱声的猫,但服务器崩溃了。 或者 100 台服务器。 怎么出去? 我们真的要让用户没有猫吗?

情况很可怕,但我们已做好准备。 我按顺序告诉你吧。

搜索基础设施位于多个数据中心:

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

在设计时,我们考虑了关闭一个数据中心的可能性。 生活充满了惊喜 - 例如,挖掘机可以切断地下电缆(是的,确实发生过)。 其余数据中心的容量应足以承受峰值负载。

让我们考虑单个数据中心。 每个数据中心都有相同的平衡器运行方案:

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么
一台平衡器至少是三台物理服务器。 这种冗余是为了可靠性。 平衡器在 HAProx 上运行。

我们选择 HAProx 是因为它具有高性能、低资源需求和广泛的功能。 我们的搜索软件在每台服务器内运行。

一台服务器发生故障的可能性很低。 但如果您有很多服务器,那么至少一台服务器出现故障的可能性就会增加。

这就是现实中发生的情况:服务器崩溃。 因此,有必要不断监控所有服务器的状态。 如果服务器停止响应,它会自动断开流量。 为此,HAProxy 具有内置的健康检查。 它每秒向所有服务器发送一次 HTTP 请求“/ping”。

HAProxy 的另一个功能:代理检查允许您均匀地加载所有服务器。 为此,HAProxy 连接到所有服务器,它们根据当前负载(从 1 到 100)返回其权重。权重是根据队列中待处理的请求数和处理器上的负载计算的。

现在关于寻找猫。 搜索结果的请求如下: /搜索?文本=愤怒+猫。 为了使搜索速度更快,整个 cat 索引必须适合 RAM。 即使从 SSD 读取也不够快。

曾几何时,报价数据库很小,一台服务器的 RAM 就足够了。 随着报价基础的增长,所有内容都不再适合此 RAM,数据被分为两部分:分片 1 和分片 2。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么
但这种情况总是会发生:任何解决方案,即使是一个好的解决方案,也会引起其他问题。

平衡器仍然会转到任何服务器。 但在请求来的机器上,索引只有一半。 其余的在其他服务器上。 因此,服务器必须转到某些相邻的计算机。 从两台服务器接收到数据后,将结果合并并重新排名。

由于平衡器均匀分配请求,因此所有服务器都参与重新排名,而不仅仅是发送数据。

如果相邻服务器不可用,就会出现此问题。 解决方案是将多个具有不同优先级的服务器指定为“相邻”服务器。 首先,请求被发送到当前机架中的服务器。 如果没有响应,则请求将发送到该数据中心的所有服务器。 最后,该请求发送至其他数据中心。
随着提案数量的增加,数据被分为四个部分。 但这还不是极限。

目前,使用八个分片的配置。 另外,为了节省更多内存,索引被分为搜索部分(用于搜索)和片段部分(不参与搜索)。

一台服务器仅包含一个分片的信息。 因此,要搜索全索引,需要在八个包含不同分片的服务器上搜索。

服务器被分组为集群。 每个集群包含八个搜索引擎和一个代码片段服务器。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么
代码片段服务器运行带有静态数据的键值数据库。 他们需要发布文件,例如对一只发出吱吱声的猫的描述。 数据专门传输到单独的服务器,以免加载搜索服务器的内存。

由于文档 ID 仅在一个索引内是唯一的,因此可能会出现片段中没有文档的情况。 嗯,或者说一个ID会有不同的内容。 因此,为了使搜索正常工作并返回结果,整个集群需要保持一致性。 下面我将告诉您我们如何监控一致性。

搜索本身的结构如下:搜索请求可以到达八个服务器中的任何一个。 假设他来到服务器 1。该服务器处理所有参数并了解要查找的内容和方式。 根据传入的请求,服务器可以向外部服务发出额外的请求以获取必要的信息。 一个请求后最多可以有十个对外部服务的请求。

收集必要的信息后,开始在报价数据库中搜索。 为此,需要对集群中的所有八台服务器进行子查询。

一旦收到响应,结果就会被合并。 最后,可能需要对代码片段服务器进行更多子查询才能生成结果。

集群内的搜索查询如下所示: /shard1?text=愤怒+猫。 此外,集群内的所有服务器之间每秒不断地进行一次以下形式的子查询: /地位.

请求 /地位 检测到服务器不可用的情况。

它还控制所有服务器上的搜索引擎版本和索引版本相同,否则会出现集群内数据不一致的情况。

尽管一个片段服务器处理来自八个搜索引擎的请求,但其处理器的负载非常轻。 因此,我们现在将片段数据传输到单独的服务。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

为了传输数据,我们引入了文档通用密钥。 现在不可能出现用一个键返回另一个文档的内容的情况。

但向另一种架构的过渡尚未完成。 现在我们想摆脱专用的片段服务器。 然后完全脱离集群结构。 这将使我们能够继续轻松扩展。 额外的好处是可以节省大量的铁。

现在来谈谈结局美好的恐怖故事。 让我们考虑几种服务器不可用的情况。

发生了可怕的事情:一台服务器不可用

假设一台服务器不可用。 那么集群中剩余的服务器可以继续响应,但搜索结果将不完整。

通过状态检查 /地位 相邻服务器知道其中一台服务器不可用。 因此,为了保持完整性,集群中的所有服务器每个请求 /平 他们开始向平衡器做出回应,称他们也不可用。 事实证明,集群中的所有服务器都死掉了(这不是事实)。 这是我们集群方案的主要缺点 - 这就是我们想要摆脱它的原因。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

因错误而失败的请求将由其他服务器上的平衡器重新发送。
平衡器还会停止向失效服务器发送用户流量,但会继续检查其状态。

当服务器可用时,它开始响应 /平。 一旦来自失效服务器的 ping 的正常响应开始到达,平衡器就开始向那里发送用户流量。 集群运行已恢复,万岁。

更糟糕的是:许多服务器不可用

数据中心的很大一部分服务器被削减。 做什么,往哪里跑? 平衡器再次来救援。 每个平衡器不断地在内存中存储当前活动服务器的数量。 它不断计算当前数据中心可以处理的最大流量。

当数据中心中的许多服务器出现故障时,平衡器意识到该数据中心无法处理所有流量。

然后多余的流量开始随机分配到其他数据中心。 一切顺利,每个人都很高兴。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

我们如何做:发布版本

现在我们来谈谈如何发布对服务所做的更改。 在这里,我们采取了简化流程的方法:推出新版本几乎完全自动化。
当项目中累积一定数量的更改时,会自动创建新版本并开始构建。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

然后将该服务推出进行测试,检查操作的稳定性。

同时启动自动性能测试。 这是由特殊服务处理的。 我现在不谈它——它的描述值得单独写一篇文章。

如果测试发布成功,将自动开始在 prestable 中发布该版本。 Prestable 是一个特殊的集群,引导正常用户流量。 如果返回错误,平衡器会向生产重新发出请求。

在 prestable 中,响应时间被测量并与生产中的先前版本进行比较。 如果一切正常,则有人进行连接:检查负载测试的图表和结果,然后开始投入生产。

祝用户一切顺利:A/B 测试

服务的改变是否会带来真正的好处并不总是显而易见的。 为了衡量变更的有用性,人们提出了 A/B 测试。 我将向您介绍一下它在 Yandex.Market 搜索中的工作原理。

这一切都始于添加启用新功能的新 CGI 参数。 让我们的参数为: 市场新功能=1。 然后在代码中,如果存在该标志,我们将启用此功能:

If (cgi.experiments.market_new_functionality) {
// enable new functionality
}

新功能正在投入生产。

为了自动化 A/B 测试,有一个专门的服务提供详细信息 此处描述。 在服务中创建了一个实验。 流量份额设置为例如15%。 百分比不是为查询设置的,而是为用户设置的。 还指出了实验的持续时间,例如一周。

可以同时运行多个实验。 在设置中,您可以指定是否可以与其他实验交叉。

结果,服务自动添加一个参数 市场新功能=1 到 15% 的用户。 它还会自动计算选定的指标。 实验完成后,分析人员查看结果并得出结论。 根据调查结果,决定投入生产或改进。

市场的灵巧之手:生产中的测试

经常发生这样的情况:您需要在生产中测试新功能的运行情况,但您不确定它在重负载下的“战斗”条件下会如何表现。

有一个解决方案:CGI 参数中的标志不仅可以用于 A/B 测试,还可以用于测试新功能。

我们制作了一个工具,允许您立即更改数千台服务器上的配置,而不会使服务面临风险。 这就是所谓的“停止点击”。 最初的想法是能够在没有布局的情况下快速禁用某些功能。 然后该工具不断扩展并变得更加复杂。

服务流程图如下:

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

标志值通过 API 设置。 管理服务将这些值存储在数据库中。 所有服务器每十秒访问一次数据库,提取标志值并将这些值应用于每个请求。

在停止点击中,您可以设置两种类型的值:

1) 条件表达式。 当其中一个值为 true 时应用。 例如:

{
	"condition":"IS_DC1",
	"value":"3",
}, 
{
	"condition": "CLUSTER==2 and IS_BERU", 
	"value": "4!" 
}

当在位置 DC3 处理请求时,将应用值“1”。 当在 beru.ru 站点的第二个集群上处理请求时,该值为“4”。

2)无条件的价值观。 如果不满足任何条件,则默认应用。 例如:

价值,价值!

如果某个值以感叹号结尾,则该值具有更高的优先级。

CGI参数解析器解析URL。 然后应用 Stop Tap 中的值。

应用具有以下优先级的值:

  1. 通过停止点击(感叹号)提高优先级。
  2. 来自请求的值。
  3. 停止点击的默认值。
  4. 代码中的默认值。

条件值中指示了许多标志 - 它们足以满足我们已知的所有场景:

  • 数据中心。
  • 环境:生产、测试​​、影子。
  • 地点:贝鲁市场。
  • 簇号。

使用此工具,您可以在一组特定服务器(例如,仅在一个数据中心)上启用新功能并测试此功能的运行,而不会给整个服务带来任何特定风险。 即使你在某个地方犯了严重的错误,一切都开始崩溃,整个数据中心都瘫痪了,平衡器也会将请求重定向到其他数据中心。 最终用户不会注意到任何事情。

如果您发现问题,可以立即将标志返回到之前的值,并且更改将回滚。

这项服务也有其缺点:开发人员非常喜欢它,并且经常尝试将所有更改推送到 Stop Tap 中。 我们正在努力打击滥用行为。

当您已经拥有准备好投入生产的稳定代码时,Stop Tap 方法效果很好。 同时,你仍然心存疑虑,想要在“战斗”条件下检查代码。

但是,Stop Tap 不适合在开发期间进行测试。 开发人员有一个单独的集群,称为“影子集群”。

秘密测试:影子集群

来自集群之一的请求将被复制到影子集群。 但平衡器完全忽略来自该集群的响应。 其操作图如下所示。

Yandex.Market 搜索的工作原理以及其中一台服务器发生故障时会发生什么

我们得到了一个处于真实“战斗”条件下的测试集群。 正常的用户流量都去那里。 两个集群中的硬件相同,因此可以比较性能和错误。

由于平衡器完全忽略响应,因此最终用户将看不到来自影子集群的响应。 因此,犯错误并不可怕。

发现

那么,我们如何构建市场搜索呢?

为了使一切顺利进行,我们将功能分为单独的服务。 这样我们就可以只扩展我们需要的那些组件并使组件更简单。 将单独的组件分配给另一个团队并分担处理该组件的责任很容易。 通过这种方法可以显着节省铁的使用,这是一个明显的优势。

影子集群还帮助我们:我们可以开发服务,在过程中测试它们,而不打扰用户。

嗯,当然是在生产中进行测试。 需要更改数千台服务器上的配置? 很简单,使用“停止水龙头”。 这样,您可以立即推出现成的复杂解决方案,并在出现问题时回滚到稳定版本。

我希望我能够展示我们如何通过不断增长的报价基础使市场快速稳定。 我们如何解决服务器问题、处理大量请求、提高服务的灵活性并在不中断工作流程的情况下做到这一点。

来源: habr.com

添加评论