Press "Enter" to skip to content

系统设计系列:从零开始构建高性能数据流系统的终极指南!

来源:Unsplash

建立一个样例问题:推荐系统

“数据流”听起来非常复杂,而“数据流水线”更是如此。在我们谈论它的意义并为自己增添术语之前,让我们从任何软件系统存在的原因开始,即问题。

我们的问题非常简单,我们需要为电子商务网站(类似于亚马逊)构建一个推荐系统,即根据用户的喜好为用户返回一组产品的服务。我们暂时不需要过于疲劳地探讨它的工作原理(稍后详述),现在,我们将专注于数据如何传输到这个服务,以及它如何返回数据。

数据以“事件”的形式发送到服务中。每个事件都是用户执行的特定操作。例如,点击一个特定产品或搜索查询。简单来说,我们网站上的所有用户互动,从简单的滚动到昂贵的购买,都被视为一个“事件”。

作者提供的图像

这些事件实质上告诉我们有关用户的信息。例如,有兴趣购买游戏电脑的用户可能也对游戏键盘或鼠标感兴趣。

定期,我们的服务会收到一个请求,要求为某个用户获取推荐,它的任务很简单,以用户感兴趣的产品列表作为响应。

作者提供的图像

就目前而言,我们暂时不关心这个推荐列表如何生成,假设这个“推荐服务”执行了一些神奇的步骤(稍后在本文末尾详述这个魔法,目前我们不太关心这些步骤的逻辑),并确定了用户的偏好。

推荐通常是许多系统中的后期想法,但它比你想象的要重要得多。您使用的几乎每个应用程序都严重依赖这些推荐服务来推动用户的行为。例如,根据这篇论文显示,亚马逊网络销售的35%是通过他们推荐的商品产生的。

然而,问题在于数据的规模之巨。即使我们只运行一个相对受欢迎的网站,峰值时仍可能接收到数十万甚至数百万个事件!而如果有新产品或大型促销活动,数据量可能更高。

而且我们的问题并不止于此。我们需要实时处理这些数据(执行之前提到的“魔法”)并实时向用户提供推荐!如果有促销活动,即使在更新推荐时延迟几分钟,也可能给企业带来重大的财务损失。

什么是数据流水线?

数据流水线就是我上面描述的那样。它是一个系统,接收连续的数据(例如事件),执行多个处理步骤,并将结果存储以备将来使用。

在我们的情况下,事件将来自多个服务,我们的处理步骤将涉及一些“神奇”的步骤来计算关于用户的推荐,然后我们将在数据存储中更新每个用户的推荐。当我们收到对特定用户的推荐查询时,我们只需获取之前存储的推荐并返回。

本文的目的是理解如何处理这样规模的数据,如何摄取、处理并输出以供以后使用,而不是理解处理步骤的实际逻辑(但我们仍然会浅入其中以增加乐趣)。

创建数据流处理管道:逐步

我们有很多东西要讨论,摄取、处理、输出和查询,所以让我们一步一步来。将每个步骤视为一个较小、孤立的问题。在每个步骤中,我们将从最直观的解决方案开始,看看为什么它不起作用,然后构建一个有效的解决方案。

数据摄取

让我们从管道的起点开始,数据摄取。数据摄取问题很容易理解,目标只是从多个源摄取事件。

Image by Author

尽管这个问题一开始看起来很简单,但它也有其自身的细微差别:

  1. 数据规模非常大,每秒可以轻松达到数十万个事件。
  2. 所有这些事件都必须实时摄取,我们不能有几秒钟的延迟。

让我们从简单的方式开始,最直观的方法是将每个事件作为请求发送到推荐系统,但这个解决方案存在许多问题:

  1. 发送事件的服务不应该等待推荐服务的响应。这将增加服务的延迟并阻塞它们,直到推荐服务发送一个 200 响应。它们应该发送“发送后即忘记”的请求。
  2. 事件数量会非常不稳定,全天候上下波动(例如,晚上或促销期间增加),我们必须根据事件规模扩展我们的推荐服务。这是我们需要管理和计算的事情。
  3. 如果我们的推荐服务崩溃,则在其崩溃期间会丢失事件。在这个架构中,我们的推荐服务是一个单点故障。

通过使用消息代理或类似 Apache Kafka 的“事件流平台”来解决这个问题。如果你不知道 Kafka 是什么,它只是一个你设置的工具,可以从“发布者”那儿接收特定主题的消息。订阅者会监听或订阅一个主题,每当在主题上发布了消息,订阅者就会接收到消息。我们将在下一节中更多地讨论 Kafka 主题。

关于 Kafka,你需要了解的是它在生产者和消费者之间实现了松耦合的架构。生产者可以在 Kafka 主题上发布消息,它们不需要关心消费者何时、如何或是否消费消息。消费者可以在自己的时间内消费并处理消息。Kafka 还能够很好地扩展,因为它可以水平和线性地扩展,提供几乎无限的扩展能力(只要我们不断添加更多的机器)

Image by Author

因此,每个服务都将事件发送到 Apache Kafka。推荐服务从 Kafka 中获取这些事件。让我们看看这如何帮助我们 –

  1. 事件被异步地处理,服务不再需要等待来自推荐服务的响应。
  2. 扩展 Kafka 更容易,如果事件规模增加,Kafka 只会在我们扩展推荐服务的同时存储更多的事件。
  3. 即使推荐服务崩溃,我们也不会丢失任何事件。事件在 Kafka 中持久保存,因此我们永远不会丢失任何数据。

现在我们知道了如何将事件摄取到我们的服务中,让我们转向架构的下一部分,处理事件。

数据处理

数据处理是我们数据管道的一个重要部分。一旦我们接收到事件,我们需要为用户生成新的推荐。例如,如果用户搜索“显示器”,我们需要根据这个搜索来更新该用户的推荐,可能添加用户对显示器感兴趣的信息。

在我们更多地讨论架构之前,让我们忘掉这一切,谈谈如何生成推荐。这也是机器学习的一部分,要继续阅读本文并不重要,但它很有趣,所以我会尝试给出一个非常基本的简要描述。

让我们试图更好地理解用户交互及其含义。当用户通过搜索、点击或滚动事件与我们的网站交互时,用户向我们传达了他/她的兴趣。我们的目标是理解这些交互,并利用它们来了解用户。

当你想到一个用户时,你可能会想到一个人,有姓名、年龄等等,但出于我们的目的,我们更容易将每个用户视为一个向量,或者简单地说是一组数字。这听起来很困惑(毕竟一个用户怎么能用一组数字来表示呢),但请容我解释一下,让我们看看这是如何工作的。

假设我们可以将每个用户(或他/她的兴趣)表示为二维空间中的一个点。每个轴代表我们用户的一个特征。假设X轴代表他/她喜欢旅行的程度,Y轴代表他/她喜欢摄影的程度。用户的每个动作都会影响该用户在二维空间中的位置。

假设一个用户在我们的二维空间中的初始点如下所示:

Image by Author

当用户搜索“旅行包”时,我们将点向右移动,因为这暗示用户喜欢旅行。

Image by Author

如果用户搜索相机,我们会把用户向上移动到Y轴。

我们还将每个产品表示为相同二维空间中的一个点,

Image by Author

用户在上图中的位置表示用户喜欢旅行,并且也稍微喜欢摄影。每个产品也根据它们与摄影和旅行的相关性而被放置。

由于用户和产品只是二维空间中的点,我们可以比较它们并对它们进行数学操作。例如,从上图中,我们可以找到最接近用户的产品,也就是这里的行李箱,并有信心地说它是对用户的好推荐。

上述内容是关于推荐系统的一个非常基础的介绍(在文章结尾会更多介绍)。这些向量(通常比二维更大)被称为嵌入(用来代表我们的用户的用户嵌入以及代表我们网站上的产品的产品嵌入)。我们可以使用不同类型的机器学习模型生成它们,关于它们的内容远比我描述的要多,但基本原则是相同的。

让我们回到我们的问题。对于每个事件,我们需要更新用户嵌入(将用户在我们的n维图表上移动),并返回相关产品作为推荐。

让我们为每个事件思考一些基本步骤,以生成这些嵌入:

  1. update-embeddings: 更新用户的嵌入
  2. gen-recommendations: 获取与用户嵌入相关(或接近)的产品
  3. save: 保存生成的推荐和事件

我们可以为每种类型的事件构建一个Python服务。

Image by Author

这些微服务将会监听一个Kafka主题,处理事件,并将其发送到下一个主题,那里会有另一个服务在监听。

Image by Author

由于我们再次使用Kafka而不是发送请求,这种架构也给我们带来了我们之前讨论过的所有优势。没有单个Python微服务是单点故障,更容易处理规模。最后一个服务save-worker必须保存推荐结果以备将来使用。让我们看看它是如何工作的。

数据接收点

一旦我们处理了一个事件,并为其生成了推荐结果,我们需要将事件和推荐数据存储起来。在决定将事件和推荐数据存储在哪里之前,让我们考虑一下数据存储的要求

  1. 可扩展性和高写入吞吐量-请记住我们有很多传入事件,每个事件还更新用户的推荐结果。这意味着我们的数据存储应该能够处理非常多的写入。我们的数据库应该具有高度的可扩展性,并且应该能够线性缩放。
  2. 简单查询-我们不会执行复杂的JOIN操作或执行不同类型的查询。我们的查询需求相对简单,给定一个用户,返回预先计算的推荐列表
  3. 不需要ACID-我们的数据库不需要具有强大的ACID兼容性。它不需要任何一致性,原子性,隔离性和持久性的保证。

简单地说,我们关心的是一个能够处理巨大规模的数据库,没有额外的花哨功能。

Cassandra是满足这些要求的理想选择。由于其分布式架构,它可以线性缩放,并且可以扩展以适应非常高的写入吞吐量,这正是我们所需的。

我们可以使用两个表,一个用于存储每个用户的推荐结果,另一个用于存储事件。最后一个Python微服务save工作者将保存事件和推荐数据到Cassandra中。

Image by Author

查询

查询非常简单。我们已经为每个用户计算并持久化了推荐结果。要查询这些推荐结果,我们只需要查询我们的数据库,并获取特定用户的推荐结果。

Image by Author

完整架构

就是这样!我们已经完成了整个架构,让我们绘制出完整的架构并看看它是什么样子。

Image by Author

了解更多

Kafka

Kafka是LinkedIn开发的一个非常强大的工具,用于处理极大规模的数据流(LinkedIn在2015年的此篇博文中提到每秒约1300万条消息!)

Kafka在线性扩展和处理非常高的规模方面表现出色,但要构建这样的系统,工程师需要了解和理解Kafka,它是什么,如何工作以及与其他工具相比的效果如何。

我写了一篇博文,详细解释了Kafka是什么,它与消息代理的区别以及LinkedIn工程师撰写的原始Kafka论文的摘录。如果你喜欢这篇博文,可以看看我关于Kafka的文章-

系统设计系列:从一万英尺来看Apache Kafka

让我们来看看Kafka是什么,它是如何工作的以及我们何时应该使用它!

betterprogramming.pub

Cassandra

Cassandra是一种专为处理非常高的写入吞吐量而设计的独特数据库。它之所以能够处理如此高的吞吐量,是因为它具有高度可扩展的分散式架构。我最近写了一篇关于Cassandra如何工作以及何时使用它和何时不使用它的博文-

系统设计解决方案:何时使用Cassandra,何时不使用

关于何时使用Cassandra和何时不使用的一切你需要知道的

VoAGI.com

推荐系统

推荐系统是一项惊人的技术,几乎在我们今天使用的所有应用中都有使用。在任何系统中,个性化和推荐系统构成了用户搜索和发现流程的核心。

我写了很多关于搜索系统的文章,并且稍微提到了如何在搜索系统中构建基本的个性化问题,但我的下一个主题将更深入地研究推荐引擎,它们的工作原理以及如何设计它们。如果这听起来对你有趣,请关注我的VoAGI获取更多内容!我还在LinkedIn上发布很多精彩的内容供大家阅读,例如,这篇关于Kafka Connect的帖子,它描述了Kafka Connect的工作原理以及为什么它只需要一个简单的图表就能如此受欢迎。

结论

希望你喜欢这篇文章,如果对文章有任何反馈或对我接下来应该讨论的内容有任何想法,都可以留言!

Leave a Reply

Your email address will not be published. Required fields are marked *