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

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

这里的总体思路是:
  • 数据库中存储了基本产品。
  • 可以将一种定制级别应用于此产品-例如有关该产品在销售地区的特定版本的信息。
  • 可以在其中应用第二级自定义-例如有关区域内特定商店的产品信息。

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

    之所以出现此问题,是因为我可能需要通过以下几种方法来使数据无效:
  • 如果基本产品数据发生更改,则整个树都需要无效,并且所有内容都需要重新生成。 I can achieve this by storing the whole structure in a hash and deleting the hash by its key
  • 如果产品的第一组定制更改(即中间级别),那么我也需要使该级别下的节点无效。例如,如果CustomizedProductA的定制受到更改的影响,则需要使CustomizedProductA,CustomizedProductA1和CustomizedProductA2过期。
  • 如果产品的第二组自定义更改(即最低级别),则该节点需要无效。我可以通过调用HDEL key field(例如HDEL product CustomisedProductA:CustomisedProductA1)来实现此目的。

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

    最佳答案

    至少有3种不同的方式,每种方式各有利弊。

    第一种方法是对树使用非原子性的临时扫描,以识别和取消(删除)树的第二级(第一组自定义设置)。为此,请为您的Hash字段使用层级命名方案,并使用 HSCAN 遍历它们。例如,假设哈希的键名是产品的ID(例如ProductA),则将第一个自定义版本的第一个版本的字段名使用“0001:0001”,将其第二个版本的字段使用“0001:0002”,等等。同样,'0002:0001'将是第二个自定义的第一个版本,以此类推...然后,确实找到所有自定义42的版本,使用HSCAN ProductA 0 MATCH 0042:* HDEL 答复中的字段,并重复直到光标归零。

    相反的方法是主动“索引”每个定制的版本,以便您可以高效地获取它们,而不用执行哈希的完整扫描。解决方法是使用Redis的集合-您保留一个集合,其中包含给定产品版本的所有字段名称。版本可以是顺序的(例如在我的示例中),也可以是其他任何版本,只要它们是唯一的即可。维护这些索引的代价是-每当您添加或删除产品的自定义和/或版本时,都需要保持与这些集合的一致性。例如,创建版本将类似于:

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

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

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

    要释放排序集的功能,您将使用字典顺序,因此所有排序集的成员都应具有相同的分数(例如,使用0)。就像哈希一样,每种产品都将以有序集表示。 Set的成员与Hash字段的等效项,即自定义版本。 “技巧”以允许您执行范围搜索(或如果可能的话,则为2级无效)的方式构造成员。这是一个看起来像的示例(请注意,这里的键ProductA不是哈希,而是排序集):
    ZADD ProductA 0 0001:0001:<JSON>
    

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

    08-03 18:08