假设您正在为家居改善产品(例如钉子、螺丝、木材、瓷砖、腻子刀等)开发一个电子商务网站。这类商店(实体店或线上)通常销售种类繁多的产品。值得注意的是,当人们从这样的商店购买东西时,以后很可能需要更多相同的物品——因为谁知道一个项目到底需要多少钉子呢?
提供快速且易于使用的购买历史记录对于提供良好的用户体验至关重要,但魔鬼在于细节。一种简单的方法是按时间倒序排列物品列表,但这在几件物品之后可能会让人感到沮丧。客户真正需要的是一种有效的方式来搜索单个用户的购买历史记录。
一种方法可能是保留一个您曾经库存的所有产品的列表,并为每个物品关联一个表格行,然后对关系数据库系统进行简单的全文搜索。不幸的是,关系数据库的全文搜索功能通常不如真正的全文搜索引擎强大。
另一种选择是使用一个真正的搜索引擎,它与一个搜索索引一起工作,该索引包含用户名以及产品名称、描述和购买时间——每个都作为一个文档。如果您将结果限制在特定用户,这将为您提供真正的全文搜索功能。不幸的是,这种方法可能很难实现,因为随着每次购买都将越来越多的内容添加到单个索引中,索引的大小会迅速增长,并且每次搜索都需要查询所有用户曾经进行的每次购买。让我们评估一下索引的大小
客户数量 | 每个客户平均购买的物品数量 | 文档数量 |
500,000 | 150 | 75,000,000 |
即使是相对较少的用户数量,这种类型的索引也会迅速失控。当您拥有 500 万用户时会发生什么?1500 万用户呢?
此功能的使用模式非常有趣——在所有用户中,在任何给定时间可能都存在相当正常的分布使用情况。如果没有考虑清醒时间,您可以假设它不会出现激增。从侧面思考,单个用户可能非常不频繁地使用此功能。事实上,在大多数情况下,每个用户每周在您的电子商务平台上花费的时间不会超过几分钟。在任何给定时间,只有一小部分搜索索引会被使用。虽然通用网站搜索可能应该始终对所有用户可用,但购买历史搜索本质上是一个仅限登录用户的功能。
那么,如果我们可以为每个用户创建一个动态搜索索引——当用户登录网站时,购买搜索历史记录会从另一个数据存储中填充,然后标记为在特定时间段后过期,就像他们的会话一样?如果用户手动注销,您可以安全地删除搜索索引。
这种模式需要一个支持以下功能的搜索引擎:
如果您具备所有这些功能,那么它会为您带来什么?让我们重新审视我们的简单数学,但让我们添加一个额外的假设:2% 的用户群在任何给定时间都处于登录状态。
500,000 的 2% | 每个客户平均购买的物品数量 | 文档数量 |
10,000 个活跃用户 | 150 | 1,500,000 |
这个文档数量远比最初的策略更易于管理。此外,它的扩展基于网站的实际使用情况,而不是累积的购买历史记录。因此,如果您的网站变得更加繁忙(通常是一件好事),那么您可以随着业务的增长来扩展购买历史记录搜索。
由于购买历史记录很少发生变化,因此图片中的另一个数据存储不需要非常复杂或高性能——您只在用户登录或购买商品时访问它。它可以像一个平面文件一样简单。
由于您正在 Redis 网站上阅读本文,您可能想象到您可以在搜索和查询中使用这种模式。事实上,Redis 具有多个属性和功能,非常适合这种用例。首先,由于搜索和查询是 Redis 的一项功能,因此它继承了 Redis 本身的许多性能特性。与 Redis 一样,搜索和查询首先是内存中的,这意味着写入和读取处于更平等的地位,与需要更长时间更改或删除数据的基于磁盘的系统不同。将购买历史记录快速填充到搜索和查询中不是一个性能问题。此外,搜索和查询经过优化,可以快速创建索引以及删除或使索引过期。
深入研究,让我们看看如何按用户为基础创建索引
> FT.CREATE history:user:1234 TEMPORARY 3600 SCHEMA title TEXT description TEXT purchased NUMERIC
创建此索引的唯一不寻常之处在于TEMPORARY参数。它告诉搜索和查询使搜索索引短暂,并在指定的时间(3600 秒或与您的会话超时时间相一致的任何时间)后删除它。只要使用搜索索引,添加/删除文档或查询,就会重置空闲计时器。一旦时间过期,索引将被删除。另外请注意,索引名称包含用户标识符。
在登录时,索引将使用来自另一个数据源的FT.ADD填充。这里不需要特殊操作——搜索和查询 将把文档和键作为临时处理,无需其他语法。添加文档将很快——对于大多数文档,时间范围将在低个位数毫秒内。这不需要同步完成,因此当用户最初浏览网站时,购买历史记录可以在后台加载。
关于搜索和查询的一般说明,特别是这种多索引环境中应该重申的一点:所有文档名称在所有索引中都应该是唯一的,以防止在哈希级别出现键冲突。最后,在某些情况下,您可以使用FT.ADD上的NOSAVE选项来节省空间。这不会存储文档,而只是对其进行索引,在FT.SEARCH上只为您提供文档 ID,尽管这确实使结果检索过程变得复杂。
实现搜索功能本身非常简单。将用户输入作为查询参数传递给FT.SEARCH。唯一与任何搜索和查询 实现不同的是,索引名称将以某种方式从用户标识符派生。
当用户明确退出服务时,FT.DROP命令将删除索引和文档。严格来说,这不是必需的操作,因为TEMPORARY索引会自动过期,但是使用明确的FT.DROP会更快地释放资源。
这种特定模式并不局限于电子商务应用程序。只要您有一组需要为特定用户搜索的个性化文档,这种模式都是可行的。想象一下金融门户网站的账单或发票搜索。每个用户将只有一小部分特定于他们的文档,但搜索体验对于查找特定信息至关重要。同时,在消息应用程序中,您可能希望搜索您的聊天记录,同样,这只有在您与应用程序交互时才需要,并且这种搜索是特定于单个用户的聊天记录的。
这种模式提供了一种优化用户体验的方法,而无需创建庞大而笨重的全局索引,这种全局索引可能难以维护和扩展。这种能力取决于能够创建许多轻量级索引,这些索引具有过期时间,以及能够快速地即时索引文档。
要开始使用此模式,请下载Redis 在 redisearch.io 上或在Redis 大学上学习 Redis 搜索和查询。