如何使 Redis 缓存中数据层次结构(树)的某些部分失效



我有一些产品数据,我需要在 Redis 缓存中存储多个版本。数据由 JSON 序列化对象组成。获取纯(基本)数据的过程很昂贵,将其自定义为不同版本的过程也很昂贵,因此我想缓存所有版本以尽可能进行优化。数据结构如下所示:

                                    BaseProduct
                                         /
                                      /      
                                   /            
                                /                  
                             /                        
           CustomisedProductA                          CustomisedProductB
                  /                                         /  
CustomisedProductA1  CustomisedProductA2   CustomisedProductB1  CustomisedProductB2

这里的总体思路是:

  • 数据库中存储了一个基本产品。
  • 可以对此产品应用一个级别的定制 - 例如,有关销售区域此产品特定版本的信息。
  • 可以应用第二级定制 - 例如,在区域内的特定商店中有关该产品的信息。

以这种方式存储数据是因为数据检索/计算过程的每个步骤都很昂贵。首次为某个区域检索特定产品时,将执行一组自定义以使其成为特定于区域的产品。首次为商店检索特定产品时,我需要根据区域产品执行自定义以生成特定于商店的产品。

出现此问题的原因是我可能需要以几种方式使数据失效:

  • 如果基本产品数据发生更改,则需要使整个树失效,并且需要重新生成所有内容。我可以通过将整个结构存储在哈希中并通过其键删除哈希来实现这一点。
  • 如果产品的第一组自定义更改(即中间级别),那么我也需要使此级别下的节点无效。例如,如果自定义产品 A 的自定义受更改影响,我需要使自定义产品 A、自定义产品 A1 和自定义产品 A2 过期。
  • 如果产品的第二组自定义更改(即底层),则该节点需要失效。我可以通过调用HDEL key field在哈希中实现这一点(例如 HDEL product CustomisedProductA:CustomisedProductA1 )。

因此,我的问题是:有没有一种方法来表示这种类型的多级数据结构,以允许将数据存储在多个级别中,同时仅使树的一部分失效?或者,我是否仅限于使整个树(DEL key)或特定节点(HDEL key field)过期,但两者之间没有任何内容?

至少有 3 种不同的方法可以做到这一点,每种方法都有自己的优点和缺点。

第一种方法是使用树的非原子临时扫描来识别树的第 2 级(第一组自定义项)并使其无效(删除)。为此,请对 Hash 的字段使用分层命名方案,并使用 HSCAN 循环访问它们。例如,假设 Hash 的键名称是产品的 ID(例如 ProductA),则可以使用"0001:0001"之类的内容作为第一个自定义的第一个版本的字段名称,使用"0001:0002"作为其第二个版本的字段名称,依此类推。同样,'0002:0001' 将是第二个自定义第一个版本,依此类推......然后,找到自定义 42 的所有版本,使用 HSCAN ProductA 0 MATCH 0042:*HDEL回复中的字段,然后重复直到光标归零。

相反的方法是主动"索引"每个自定义项的版本,以便您可以有效地获取它们,而不是执行 Hash 的完全扫描。解决这个问题的方法是使用 Redis 的集合 - 你保留一个包含给定产品版本的所有字段名称的集合。版本可以是顺序的(如我的示例),也可以是其他任何内容,只要它们是唯一的。维护这些索引的成本 - 每当您添加或删除产品的自定义和/或版本时,您都需要与这些集保持一致。例如,版本的创建如下所示:

HSET ProductA 0001:0001 "<customization 1 version 1 JSON payload"
SADD ProductA:0001 0001

请注意,这两个操作应该在单个事务中(即使用MULTIEXEC块或EVAL Lua脚本)。进行此设置后,使自定义无效只需在相关集合上调用SMEMBERS并从哈希(以及集合本身)中删除其中的版本。但是,重要的是要注意,从大型集合中读取所有成员可能很耗时 - 1K 成员并不是那么糟糕,但对于较大的集合,则SSCAN

最后,您可以考虑使用排序集而不是哈希。虽然在此用例中可能不太直观,但排序集将允许您执行所需的所有操作。然而,使用它的代价是与哈希的O(1)相比,O(logN)用于添加/删除/读取的复杂性增加,但考虑到数字,差异并不显着。

为了释放排序集的力量,您将使用字典排序,以便排序集的所有成员都应具有相同的分数(例如使用 0)。每个产品将由一个排序集表示,就像哈希一样。Set 的成员是哈希字段的等效项,即自定义项的版本。"诀窍"是以一种允许您执行范围搜索(或 2 级失效,如果您愿意)的方式构建成员。下面是它的外观示例(请注意,此处的关键 ProductA 不是哈希,而是排序集):

ZADD ProductA 0 0001:0001:<JSON>

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

最新更新