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

我的团队决定通过ServiceStack.net Redis客户端与Redis合作,作为我们正在开发的一个新的高容量网站的底层存储库。 我真的不知道在哪里寻找这个问题的文档(对于一般的Redis文档或特定的ServiceStack.Net文档或两者兼而有之) – 实际上是否有关于如何通过ServiceStack.Net实现Redis的文档的权威来源所有你需要了解的有关Redis概念和ServiceStack.Net概念,还是我们需要分别从两个方面集成文档以全面了解?

我只是在想如何在模型的对象图中存储相关的对象。 以下是我想要处理的一个简单场景:

系统中有两个对象: UserFeed 。 在RDBMS术语中,这两个对象具有一对多的关系,也就是说,一个User拥有一个Feed对象的集合,一个Feed只能属于一个User 。 Feeds将始终通过其用户从Redis访问,但有时候我们希望通过Feed实例访问用户。

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

方法一

 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了 – (也没有任何人在我的团队),所以我们真的从头开始…

我不会重新编写大量的其他文档,而是围绕Redis + ServiceStack的Redis Client提供一些背景信息:

  • deviseNoSQL Redis应用程序时要考虑什么?
  • 使用RedisdeviseNoSQL数据库
  • Redis和.NET的一般概述
  • 使用C#Redis客户端进行无模式版本控制和数据迁移

没有魔法–Redis是一张空白的canvas

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

想想你会如何在代码中构build关系

因此,思考如何在Redis中存储内容的最佳方法是完全忽视数据如何存储在RDBMS表中,并考虑如何将其存储在代码中,即在内存中使用内置的C#集合类 – 这是Redis在服务器端数据结构中反映的行为。

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

可视化您的数据

为了更好地理解实体如何存储在Redis中,我build议安装Redis Admin UI ,它与ServiceStack的C#Redis客户端很好地协作,因为它使用下面的关键命名约定来提供一个很好的层次视图,将你键入的实体分组在一起(尽pipe所有的键存在于相同的全局密钥空间中)。

要查看和编辑实体,请单击编辑链接以查看和修改所选实体的内部JSON表示。 希望你一旦能看到它们的存储方式,就可以做出更好的决定。

POCO /实体如何存储

C#Redis客户端可与任何具有单个主键的POCO一起使用,默认情况下,这些主键是Id (尽pipe此公约可覆盖ModelConfig)。 实质上,POCO作为序列化的JSON与typeof(Poco).NameId用于形成该实例的唯一关键字一起存储到Redis中。 例如:

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

C#客户端中的POCO通常使用ServiceStack的快速Json串行器进行序列化 ,其中只有公共获取器的属性被序列化(公共获取器被反序列化)。

默认值可以用[DataMember] attrs覆盖,但不build议使用,因为它掩盖了您的POCO。

实体被弄脏了

因此,了解Redis中的POCO只是一团糟,您只需要将POCO上的非聚合根数据作为公共属性(除非您故意要存储冗余数据)。 一个很好的约定是使用方法来获取相关数据(因为它不会被序列化),而且会告诉你的应用程序使得远程调用哪些方法来读取数据。

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

维护自定义索引

但是,如果您想要独立保留所有的提要,即使用redisFeeds.GetById(1)那么您将希望将其存储在用户之外,并维护一个链接这两个实体的索引。

正如你已经注意到,有很多方法来存储实体之间的关系,你如何这样做主要是一个偏好的问题。 对于父子关系中的子实体,您总是希望将ParentId与子实体一起存储。 对于Parent,您可以select将模型的ChildIds集合存储起来 ,然后为所有子实体执行一次获取以重新合成模型。

另一种方法是在每个父实例的自己的Set中维护父dto外部的索引。 一些很好的例子是在Redis StackOverflow演示的C#源代码中, Users > QuestionsUsers > Answers关系存储在:

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

尽pipeC#RedisClient通过其TParent.StoreRelatedEntities() , TParent.GetRelatedEntities<TChild>()TParent.DeleteRelatedEntities() API包含对缺省父/子约定的支持,其中索引在场景后面维护,如下所示:

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

有效地,这些只是你的一些可能的select,在那里有许多不同的方法来达到同样的目的,而且你也可以自由地推出自己的select。

NoSQL的无模式,宽松的自由度应该被接受,你不应该担心试图遵循使用RDBMS时可能熟悉的严格的,预定义的结构。

总之,在Redis中存储数据是没有真正正确的方法 ,例如,C#Redis客户端做了一些假设,以便在POCO周围提供高级API,并在Redis的二进制安全string值中对POCOs进行了blob操作,尽pipe还有其他的客户端会更喜欢将实体属性存储在Redis哈希(字典)中。 两者都会工作。