设置示例问题:推荐系统
“数据流”听起来非常复杂,而“数据流管道”听起来更加复杂。在我们探讨这意味着什么并且让自己陷入术语之前,让我们从存在任何软件系统的原因,即一个问题开始。
我们的问题相当简单,我们需要为一个电子商务网站(像亚马逊那样的)构建一个推荐系统,即一个基于用户偏好为特定用户返回一套产品的服务。目前我们不需要劳神去理解它是如何工作的(稍后会详细讲解),现在,我们将关注数据是如何发送给这个服务的,以及它如何返回数据。
数据以“事件”的形式发送给服务。这些事件中的每一个都是用户执行的特定动作。例如,点击一个特定的产品,或一个搜索查询。简单来说,所有在我们网站上的用户交互,从简单的滚动到昂贵的购买,都被视为一个“事件”。
这些事件本质上告诉我们有关用户的信息。例如,一个对购买游戏电脑感兴趣的用户可能也对游戏键盘或鼠标感兴趣。
我们的服务偶尔会收到一个请求,要求为用户获取推荐商品,它的工作很简单,回应一份用户感兴趣的产品列表。
目前,我们不关心这个推荐列表是如何生成的,假设这个“推荐服务”执行了一些神奇的步骤,并找出我们的用户喜好。
推荐通常在许多系统中被视为后期考虑的事项,但它比你想象的要重要得多。几乎你使用的每一个应用程序都严重依赖于这样的推荐服务来引导用户行为。
然而,问题在于海量数据的规模。即使我们只运营一个适度受欢迎的网站,在高峰期每秒也可能会收到成千上万个(甚至百万个)事件!如果有一款新产品或者大促销,那么数字可能会更高。
我们的问题还不止于此。我们必须实时处理这些数据并实时向用户提供推荐!如果有促销活动,哪怕是几分钟的推荐更新延迟,都可能给企业带来严重的经济损失。
什么是数据流处理管道?
数据流处理管道就是我上面描述的那样。它是一个系统,可以吞吐连续的数据(如事件),执行多个处理步骤,并存储结果以供将来使用。
在我们的案例中,事件将来自多个服务,我们的处理步骤将涉及一些“神奇”的步骤来计算用户的推荐,然后我们将在数据存储中更新每个用户的推荐。当我们收到针对特定用户的推荐请求时,我们只需获取之前存储的推荐并返回它们。
本文的目的是为了理解如何处理这种规模的数据,如何摄取数据、处理数据,以及如何输出数据供以后使用,而不是为了理解处理步骤的实际逻辑。
创建数据流处理管道:分步
要讨论的内容很多,包括数据摄取、处理、输出和查询,所以让我们一步一步来。把每一步看作是一个较小的、独立的问题。在每一步,我们将从最直观的解决方案开始,看看为什么不行,然后构建一个可行的解决方案。
数据摄取
让我们从管道的开始,即数据摄取问题开始。数据摄取问题很容易理解,目标就是从多个来源摄取事件。
虽然这个问题一开始看起来很简单,但它也有相当多的细微差别:
让我们从简单的开始,实现这一目标最直观的方法是将每个事件作为一个请求发送到推荐系统,但这个解决方案有许多问题,
我们通过使用消息代理或者像Apache Kafka这样的“事件流平台”来修正这个问题。如果你不知道这是什么,简单来说它就是一个你可以设置的工具,它可以从“发布者”摄取消息到特定的主题。“订阅者”监听或订阅一个主题,每当有消息发布在该主题上时,订阅者就会收到这个消息。
你需要知道的关于Kafka的是,它促进了生产者和消费者之间的解耦架构。生产者可以在Kafka主题上发布一个消息,他们不需要关心消费者何时、如何以及是否消费了消息。消费者可以自己的时间消费消息并处理它。Kafka还能够促进非常高的规模,因为它可以水平扩展,并且线性扩展,提供几乎无限的扩展能力(只要我们不断增加更多机器)。
所以每个服务都将事件发送到Apache Kafka。推荐服务从Kafka获取这些事件。让我们看看这是如何帮助我们的:
现在我们知道如何将事件引入我们的服务,让我们进入架构的下一个部分,处理事件。
数据处理
数据处理是我们数据流水线的一个不可或缺的部分。一旦我们接收到事件,我们需要为用户生成新的推荐。例如,如果用户搜索“显示器”,我们需要根据这个搜索更新该用户的推荐,可以添加用户对显示器感兴趣。
在我们更多讨论架构之前,让我们先忘记所有这些,稍微谈谈如何生成推荐。这也是机器学习介入的地方,虽然理解它不是继续阅读文章的重点,但这非常有趣,所以我会试图给出一个非常基础的简要描述,它是怎么工作的。
让我们尝试更好地理解用户互动和它们的意义。当用户通过搜索、点击或滚动事件与我们的网站互动时,用户在告诉我们一些关于他/她的兴趣。我们的目标是理解这些互动,并使用它们来理解用户。
当你想象一个用户时,你可能会想到一个有名字、年龄等的人,但出于我们的目的,将每个用户想象成一个向量,或者简单地说,一组数字,会更容易。这听起来可能让人困惑(毕竟,用户怎么能用一组数字来表示呢?),但请耐心听我说,让我们看看它是如何工作的。
假设我们可以将每个用户(或他/她的兴趣)表示为一个二维空间中的点。每个轴代表我们用户的一个特征。假设X轴表示他/她多么喜欢旅行,Y轴表示他/她多么喜欢摄影。用户的每一个动作都会影响这个用户在二维空间中的位置。
假设一个用户在我们二维空间中开始于如下点 —
当用户搜索“旅行包”时,我们将点向右移动,因为这表明用户喜欢旅行。
如果用户搜寻了相机,我们就会将用户在Y轴上向上移动。
我们同样也将每个产品表示为在同一个二维空间中的一个点。
上图中用户的位置表明用户喜欢旅行,并且也有点喜欢摄影。每个产品也根据其与摄影和旅行的相关性而摆放。
由于用户和产品都是二维空间中的点,我们可以比较它们并对它们进行数学操作。例如,从上图中,我们可以找到最接近用户的产品,在这个例子中是行李箱,我们可以自信地说这是对用户的一个好推荐。
上述内容是对推荐系统的非常基础的介绍。这些向量(通常比2维要大得多)被称为嵌入(代表我们用户的用户嵌入,以及代表我们网站上产品的产品嵌入)。我们可以使用不同类型的机器学习模型来生成它们,尽管它们比我所描述的要复杂得多,但基本原理保持不变。
让我们回到我们的问题。对于每个事件,我们需要更新用户嵌入(在我们的n维图表上移动用户),并返回相关产品作为推荐。
让我们考虑一下我们需要执行的每个事件的一些基本步骤来生成这些嵌入,
我们可以为每种类型的事件构建一个Python服务。
这些微服务中的每一个都会监听一个Kafka主题,处理事件,然后将其发送到下一个主题,在那里另一个不同的服务将会监听。
由于我们再次使用Kafka而不是发送请求,这种架构也给了我们之前讨论过的所有优势。没有单个Python微服务是故障的单点,而且处理规模要容易得多。最后一个服务save-worker必须为将来的使用保存推荐信息。让我们看看是如何工作的。
数据接收器
一旦我们处理了一个事件,并为它生成了推荐信息,我们需要存储事件和推荐数据。在我们决定在哪里存储事件和推荐数据之前,让我们考虑数据存储的要求:
简单来说,我们关心的是一种能够处理巨大规模的数据库,没有额外的花哨功能。
Cassandra是这些要求的完美选择。它由于去中心化的架构能够线性地伸缩,并且可以扩展以适应非常高的写入吞吐量,这正是我们所需要的。
我们可以使用两个表,一个用于存储每个用户的推荐,另一个用于存储事件。最后一个Python微服务save worker将把事件和推荐数据保存在Cassandra中。
查询
查询相当简单。我们已经为每个用户计算并持久化了推荐。要查询这些推荐,我们只需要查询我们的数据库并为特定用户获取推荐。
完整架构
就这样!我们已经完成了整个架构,现在让我们绘制出完整的架构图,看看它是什么样子。