如何在Redis缓存中使层次结构(树)的部分失效

3
我有一些产品数据需要在Redis缓存中存储多个版本。该数据由JSON序列化对象组成。获取基本数据的过程很昂贵,将其定制为不同版本的过程也很昂贵,因此我希望尽可能地缓存所有版本以进行优化。数据结构大致如下:
                                    BaseProduct
                                         /\
                                      /      \
                                   /            \
                                /                  \
                             /                        \
           CustomisedProductA                          CustomisedProductB
                  /  \                                       /  \
CustomisedProductA1  CustomisedProductA2   CustomisedProductB1  CustomisedProductB2

这里的基本思路是:
  • 在数据库中存储了一个基础产品。
  • 可以对该产品应用一级定制——例如,关于特定版本产品在销售区域的信息。
  • 可以在其中应用第二级定制——例如,在区域内的特定商店中关于该产品的信息。
数据以这种方式存储,因为每个数据检索/计算步骤都很昂贵。当针对某个区域第一次检索特定产品时,会执行一组定制操作,使其成为区域特定产品。当针对某个商店第一次检索特定产品时,需要根据区域产品执行定制操作,生成商店特定产品。
问题出在我可能需要以几种方式使数据失效:
如果基础产品数据发生更改,则整个树都需要无效化并重新生成。我可以通过将整个结构存储在哈希表中,并通过其键删除哈希表来实现此目的。 如果产品的第一组定制(即中间层)发生更改,则我还需要使该级别下面的节点失效。例如,如果CustomisedProductA的定制受到更改的影响,则需要使CustomisedProductA、CustomisedProductA1和CustomisedProductA2过期。 如果产品的第二组定制(即底层)发生更改,则需要使该节点失效。我可以通过调用HDEL key field(例如,HDEL product CustomisedProductA:CustomisedProductA1)在哈希表中实现这一点。
我的问题是:是否有一种表示这种多级数据结构的方法,可以在允许将数据存储在多个级别的同时使树的部分失效?或者,我只能使整个树过期(DEL key)或特定节点(HDEL key field),而无法做到中间状态的失效?
1个回答

5

至少有三种不同的方法可以做到这一点,每种方法都有其优缺点。

第一种方法是使用非原子性的临时扫描树来识别和使无效(删除)树的第二层(第一组自定义)。为此,请使用哈希字段的分层命名方案,并使用HSCAN迭代它们。例如,假设您的哈希键名称是产品的ID(例如ProductA),则对于第一个自定义的第一个版本,您将使用“0001:0001”作为字段名称,“0001:0002”为其第二个版本,依此类推。“0002:0001”将是第二个自定义的第一个版本,等等...然后,查找所有自定义42的版本,使用HSCAN ProductA 0 MATCH 0042:* HDEL回复中的字段,并重复直到游标为零。

另一种方法是积极“索引”每个自定义版本,以便您可以高效地获取它们,而不是执行Hash的完整扫描。实现这一点的方法是使用Redis的Sets-您保留一个Set,其中包含给定产品版本的所有字段名称。版本可以是顺序的(如我的示例)或任何其他唯一的版本。成本是维护这些索引-每当您添加或删除产品的自定义和/或版本时,都需要与这些Sets保持一致。例如,版本的创建将是以下内容:
HSET ProductA 0001:0001 "<customization 1 version 1 JSON payload"
SADD ProductA:0001 0001

请注意,这两个操作应该在一个事务中完成(即使用MULTI\EXEC块或EVAL Lua脚本)。一旦设置好了,使定制失效只是调用相关集合的SMEMBERS并从哈希表中删除其中的版本(以及集合本身)。然而,需要注意的是,从大型集合中读取所有成员可能会耗时-1K成员并不那么糟糕,但对于更大的集合,最好使用SSCAN
最后,您可以考虑使用排序集合而不是哈希表。虽然在这种情况下可能不太直观,但排序集合将让您执行所需的所有操作。然而,使用它的代价是添加/删除/读取的O(logN)相对于哈希表的O(1)的复杂度增加,鉴于数字的数量,差异不太明显。
为了发挥排序集合的能力,您需要使用词典排序(lexicographical ordering),因此所有排序集合的成员都应该有相同的分数(例如使用0)。每个产品将由一个排序集合表示,就像哈希表一样。集合的成员是哈希表字段的等价物,即自定义版本。 "诀窍"在于以允许您执行范围搜索(或级别2的失效)的方式构建成员。以下是应该看起来如何的示例(请注意,这里的键ProductA不是哈希表而是排序集合):
ZADD ProductA 0 0001:0001:<JSON>

要阅读自定义版本,请使用ZRANGEBYLEX ProductA [0001:0001: [0001:0001:\xff并从回复中拆分JSON,要删除整个自定义,请使用ZREMRANGEBYLEX

树将非常大。将有一个根节点(基础产品),约30个中间节点(区域定制产品),对于每个中间节点,可能会有多达1000个其他可能的定制。并非所有这些都会被使用,但在最坏的情况下,我们可能会看到总共约30,000个节点。中间节点(区域定制产品)的失效可能每天发生两次。根节点的失效将每天发生一次。理想情况下,我们希望更新是原子性的,但如果必要,我很乐意考虑替代方案。 - John
哇,这是一个非常详细的回答。谢谢!三个选项都有道理(尽管我还在学习Redis,所以一些细节还不太清楚)。对我来说,选项2似乎是最简单的,但我会尝试其他选项,以比较它们在性能和实现复杂度方面的差异。非常感谢您的答案。 - John

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接