一尘不染

ServiceStack.Net Redis:存储相关对象与相关对象ID

c#

我的团队决定通过ServiceStack.net Redis
Client与Redis一起使用,作为我们正在处理的新的高流量网站的基础存储库。我真的不确定在哪里可以找到有关此问题的文档(对于一般的Redis文档还是特定的ServiceStack.Net文档,或两者兼而有之)-实际上是否存在有关如何通过ServiceStack.Net实现Redis的文档的权威来源,其中包括您需要了解有关Redis概念和ServiceStack.Net概念的全部知识,还是我们需要分别集成这两个方面的文档才能获得完整的概览?

我只是在努力如何在模型的对象图中准确存储相关对象。这是我要使用的简单方案:

系统中有两个对象:UserFeed。用RDBMS术语来说,这两个对象具有一对多的关系,即,一个User具有Feed对象的集合,而提要只能属于一个User。Feed始终会通过Redis的用户进行访问,但有时我们希望通过feed实例来访问该用户。

因此,我的问题是我们应该将相关对象存储为属性还是应该存储Id相关对象的值?为了显示:

方法A

public class User
{
    public User()
    {
        Feeds = new List<Feed>();
    }

    public int Id { get; set; }

    public List<Feed> Feeds { get; set; }

    // Other properties
}

public class Feed
{
    public long Id { get; set; }

    public User User { get; set; }
}

方法B

public class User
{
    public User()
    {
        FeedIds = new List<long>();
    }

    public long Id { get; set; }

    public List<long> FeedIds { get; set; }

    public List<Feed> GetFeeds()
    {
        return repository.GetFeeds( FeedIds );
    }
}

public class Feed
{
    public long Id { get; set; }

    public long UserId { get; set; }

    public User GetUser()
    {
        return repository.GetUser( UserId );
    }
}

以上哪种方法最有效?我已经在各种示例中看到了这两种方法,但是我得到的印象是,我看到的某些示例可能不是最佳实践。

一些简单的相关问题:

  • 如果我对对象进行更改,它将自动反映在Redis中还是需要保存?我假设是后者,但需要绝对清楚。
  • 如果我(可以)使用方法A,则对用户对象X的更新会在引用的所有对象图中反映到整个对象图中,还是有必要在整个图中保存更改?
  • 通过对象的接口存储对象是否存在问题(即使用IList<Feed>而不是List<Feed>

抱歉,如果这些问题有点基本-直到2周前,我什至没有听说过Redis-更不用说ServiceStack了(我的团队中也没有任何人),所以我们真的是从零开始。


阅读 265

收藏
2020-05-19

共1个答案

一尘不染

与其重新散布大量的其他文档,我不如列出一些有关Redis + ServiceStack的Redis Client的背景信息:

没有魔法-Redis是一块空白画布

首先,我想指出的是,将Redis用作数据存储仅提供了一个空白画布,并且本身没有任何相关实体的概念。即,它仅提供对分布式comp-
sci数据结构的访问。通过使用Redis的原始数据结构操作,如何存储关系最终取决于客户端驱动程序(即ServiceStack C#Redis
Client)或应用程序开发人员。由于所有主要数据结构都是在Redis中实现的,因此您基本上可以完全自由地构造和存储数据。

想想如何在代码中构建关系

因此,考虑如何在Redis中存储内容的最好方法是完全不考虑数据在RDBMS表中的存储方式,而考虑数据在代码中的存储方式,即在内存中使用内置的C#集合类-
Redis通过其服务器端数据结构来反映行为。

尽管没有相关实体的概念,Redis的内置 SetSortedSet 数据结构还是存储索引的理想方法。例如,Redis的 Set
集合最多只能存储1个元素。这意味着您可以安全地向其中添加项目/键/标识,而不必关心该项目是否已经存在,因为如果您将其调用1或100次,最终结果将是相同的-
即它是幂等的,最终仅将1个元素保留在其中集合。因此,在存储对象图(聚合根)时,一个常见的用例是每次保存模型时将子实体ID(又称为外键)存储到Set中。

可视化您的数据

为了更好地可视化实体在Redis中的存储方式,我建议安装与ServiceStack的C#Redis Client配合使用的Redis Admin
UI
,因为它使用下面的键命名约定提供了一个很好的层次结构视图,将键入的实体分组在一起(尽管所有键)存在于同一全局键空间中)。

要查看和编辑实体,请单击“ 编辑”
链接以查看和修改所选实体的内部JSON表示形式。希望一旦看到模型的存储方式,您将能够对如何设计模型做出更好的决策。

POCO /实体的存储方式

C#Redis客户端可与具有单个主键的任何POCO一起使用-
默认情况下应为主键Id(尽管此约定通过ModelConfig覆盖)。本质上,POCO作为序列化JSON存储在Redis中,同时使用typeof(Poco).NameId来为该实例形成唯一密钥。例如:

urn:Poco:{Id} => '{"Id":1,"Foo":"Bar"}'

C#客户端中的POCO通常使用ServiceStack的快速Json序列化器进行序列化,在序列化器中,仅对具有公共获取器的属性进行序列化(并通过公共获取器反序列化)。

默认值可以用[DataMember]attrs 覆盖,但不建议使用,因为它会使您的POCO变得丑陋。

实体被爆

因此,知道Redis中的POCO只是泛滥了,您只想将非聚合的根数据保留在POCO上作为公共属性(除非您有意存储冗余数据)。一个好的约定是使用方法来获取相关数据(因为它不会被序列化),但还告诉您的应用哪些方法进行了远程调用以读取数据。

因此,关于 Feed 是否应与 用户
一起存储的问题是,它是否是非聚合的根数据,即您是否要在用户上下文之外访问用户feed?如果否,则将List<Feed> Feeds属性保留在User类型上。

维护自定义索引

但是,如果您想保持所有提要独立访问,即使用,redisFeeds.GetById(1)则需要将其存储在用户外部并维护一个链接这两个实体的索引。

正如您已经注意到的,有很多方法可以存储实体之间的关系,并且如何存储很大程度上取决于偏好。对于 parent >
child
关系中的子实体,您总是希望将 ParentId 与该子实体一起存储。对于父级,您可以选择与模型一起存储 ChildId
的集合,然后对所有子实体进行一次提取以重新 补充 模型。

另一种方法是为每个父实例在其自己的 Set
中的父dto外部维护索引。这样的一些很好的例子是在C#源代码中的Redis的StackOverflow上演示其中的关系`Users

Questions,并Users > Answers`存储在:

idx:user>q:{UserId} => [{QuestionId1},{QuestionId2},etc]
idx:user>a:{UserId} => [{AnswerId1},{AnswerId2},etc]

虽然C#RedisClient不包括通过其默认的父/子惯例支持TParent.StoreRelatedEntities()
TParent.GetRelatedEntities<TChild>()以及TParent.DeleteRelatedEntities()API的其中一个指标保持在幕后,看起来像:

ref:Question/Answer:{QuestionId} => [{answerIds},..]

实际上,这些只是您可能的选择中的一些,在其中有许多种不同的方法可以达到相同的目的,并且您还可以自由滚动自己的目标。

应该拥抱NoSQL的无模式的,松散的自由,并且您不必担心遵循遵循在使用RDBMS时可能熟悉的刚性,预定义的结构。

总之,没有真正 正确的方法 在Redis中存储数据,例如,C#Redis
Client进行了一些假设,以提供围绕POCO的高级API,并且在Redis的二进制安全字符串值中使POCO斑点化-
尽管还有其他方法客户将更喜欢将实体属性存储在Redis哈希(字典)中。两者都会起作用。

2020-05-19