ServiceStack.Net Redis:存储相关对象与相关对象ID
我的团队决定通过ServiceStack.net Redis客户端与Redis合作,作为我们正在开发的一个新的高容量网站的底层存储库。 我真的不知道在哪里寻找这个问题的文档(对于一般的Redis文档或特定的ServiceStack.Net文档或两者兼而有之) – 实际上是否有关于如何通过ServiceStack.Net实现Redis的文档的权威来源所有你需要了解的有关Redis概念和ServiceStack.Net概念,还是我们需要分别从两个方面集成文档以全面了解?
我只是在想如何在模型的对象图中存储相关的对象。 以下是我想要处理的一个简单场景:
系统中有两个对象: User
和Feed
。 在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内置的Set和SortedSet数据结构提供了存储索引的理想方法。 例如,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).Name
和Id
用于形成该实例的唯一关键字一起存储到Redis中。 例如:
urn:Poco:{Id} => '{"Id":1,"Foo":"Bar"}'
C#客户端中的POCO通常使用ServiceStack的快速Json串行器进行序列化 ,其中只有公共获取器的属性被序列化(公共获取器被反序列化)。
默认值可以用[DataMember]
attrs覆盖,但不build议使用,因为它掩盖了您的POCO。
实体被弄脏了
因此,了解Redis中的POCO只是一团糟,您只需要将POCO上的非聚合根数据作为公共属性(除非您故意要存储冗余数据)。 一个很好的约定是使用方法来获取相关数据(因为它不会被序列化),而且会告诉你的应用程序使得远程调用哪些方法来读取数据。
因此, Feed是否应与用户存储在一起的问题是,它是否是非聚合根数据,即是否要访问用户上下文之外的用户提要? 如果不是,则在User
types上保留List<Feed> Feeds
属性。
维护自定义索引
但是,如果您想要独立保留所有的提要,即使用redisFeeds.GetById(1)
那么您将希望将其存储在用户之外,并维护一个链接这两个实体的索引。
正如你已经注意到,有很多方法来存储实体之间的关系,你如何这样做主要是一个偏好的问题。 对于父子关系中的子实体,您总是希望将ParentId与子实体一起存储。 对于Parent,您可以select将模型的ChildIds集合存储起来 ,然后为所有子实体执行一次获取以重新合成模型。
另一种方法是在每个父实例的自己的Set中维护父dto外部的索引。 一些很好的例子是在Redis StackOverflow演示的C#源代码中, Users > Questions
和Users > 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哈希(字典)中。 两者都会工作。