ServiceStack.Net Redis:存储相关对象与相关对象 ID [英] ServiceStack.Net Redis: Storing Related Objects vs. Related Object Ids

查看:31
本文介绍了ServiceStack.Net Redis:存储相关对象与相关对象 ID的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的团队决定通过 ServiceStack.net Redis Client 使用 Redis 作为我们正在开发的新的高容量网站的底层存储库.我不确定在哪里可以找到这个问题的文档(一般 Redis 文档或特定的 ServiceStack.Net 文档或两者) - 实际上是否有关于如何通过 ServiceStack.Net 实现 Redis 的文档的权威来源,其中包括您需要了解有关 Redis 概念和 ServiceStack.Net 概念的所有信息,还是我们需要分别从这两个方面集成文档以了解全貌?.

My team has decided to work with Redis via the ServiceStack.net Redis Client as an underlying repository for a new high-volume website we're working on. I'm not really sure where to look for documentation for this question (either for general Redis docs or specific ServiceStack.Net docs or both) - is there actually a definitive source for documentation on how to implement a Redis via ServiceStack.Net that includes all you need to know about both Redis concepts and ServiceStack.Net concepts, or do we need to integrate documentation from both aspects separately to get the full picture?.

我只是在努力解决如何在我们模型的对象图中准确存储相关对象.这是我想要使用的一个简单场景:

I'm just grappling with how exactly to store related objects in our model's object graph. Here's a simple scenario that I want to work with:

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

There are two objects in the system: User and Feed. In RDBMS terms these two objects have a one-to-many relationship, that is, a User has a collection of Feed objects and a feed can only belong to one User. Feeds will always be accessed from Redis via their user but occasionally we'll want to get access to the user via a feed instance.

所以我的问题是我们应该将相关对象存储为属性还是应该存储相关对象的 Id 值?举例说明:

So the question I have is whether we should be storing the related objects as properties or should we store the Id values of the related objects? To illustrate:

方法 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 );
    }
}

以上哪种方法效果最好?我已经在各种示例中看到了这两种方法,但我的印象是我所看到的一些示例可能不是最佳实践.

Which of the above approaches will work best? I've seen both approaches used in various examples but I get the impression that some of the examples I've seen may not be best-practice.

几个简单的相关问题:

  • 如果我对对象进行更改,它会自动反映在 Redis 中还是需要保存?我假设是后者,但需要绝对清楚.
  • 如果我(可以)使用方法 A,对用户对象 X 的更新是否会反映在整个对象图中引用它的任何地方,或者是否有必要保存整个图中的更改?
  • 通过接口存储对象是否存在问题(即使用 IList 而不是 List?
  • If I make a change to an object will it automatically be reflected in Redis or will it require a save? I'm assuming the latter, but need to be absolutely clear.
  • If I (can) use Approach A, will an update to User object X be reflected throughout the entire object graph wherever it is referenced or will it be necessary to save changes across the graph?
  • Is there a problem with storing an object via it's interface (i.e. use IList<Feed> as opposed to List<Feed>?

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

Sorry if these questions are a little basic - until 2 weeks ago I'd never even heard of Redis - let alone ServiceStack - (nor had anyone in my team) so we're really starting from scratch here...

推荐答案

我将列出一些关于 Redis + ServiceStack 的 Redis 的背景信息,而不是重新散列大量其他文档客户:

Rather than re-hash a lot of other documentation that's out there in the wild, I'll list a couple around for some background info around Redis + ServiceStack's Redis Client:

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

First I want to point out that using Redis as a data store just provides a blank canvas and doesn't have any concept of related entities by itself. i.e. it just provides access to distributed comp-sci data structures. How relationships get stored is ultimately up to the client driver (i.e. ServiceStack C# Redis Client) or the app developer, by using Redis's primitive data structure operations. Since all the major data structures are implemented in Redis, you basically have complete freedom on how you want to structure and store your data.

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

So the best way to think about how to store stuff in Redis, is to completely disregard about how data is stored in an RDBMS table and think about how it is stored in your code, i.e. using the built-in C# collection classes in memory - which Redis mirrors in behavior with their server-side data-structures.

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

Despite not having a concept of related entities, Redis's built-in Set and SortedSet data structures provide the ideal way to store indexes. E.g. Redis's Set collection only stores a max of 1 occurrence of an element. This means you can safely add items/keys/ids to it and not care if the item exists already as the end result will be the same had you called it 1 or 100 times - i.e. it's idempotent, and ultimately only 1 element remains stored in the Set. So a common use-case is when storing an object graph (aggregate root) is to store the Child Entity Ids (aka Foreign Keys) into a Set every time you save the model.

对于实体如何存储在 Redis 中的良好可视化,我建议安装 Redis Admin UI,它运行良好使用 ServiceStack 的 C# Redis 客户端,因为它使用下面的键命名约定来提供一个很好的分层视图,将您键入的实体分组在一起(尽管所有键都存在于同一个全局键空间中).

For a good visualization of how Entities are stored in Redis I recommend installing the Redis Admin UI which works well with ServiceStack's C# Redis Client as it uses the key naming convention below to provide a nice hierarchical view, grouping your typed entities together (despite all keys existing in the same global keyspace).

要查看和编辑实体,请点击编辑链接以查看和修改所选实体的内部 JSON 表示.希望您能够在了解模型的存储方式后就如何设计模型做出更好的决定.

To view and edit an Entity, click on the Edit link to see and modify the selected entity's internal JSON representation. Hopefully you'll be able to make better decisions about how to design your models once you can see how they're stored.

C# Redis 客户端适用于任何具有单个主键的 POCO - 默认情况下应该是 Id(尽管这个约定 可被 ModelConfig 覆盖).本质上,POCO 以序列化 JSON 的形式存储到 Redis 中,其中 typeof(Poco).NameId 用于形成该实例的唯一键.例如:

The C# Redis Client works with any POCOs that have a single primary key - which by default is expected to be Id (though this convention overridable with ModelConfig). Essentially POCOs gets stored into Redis as serialized JSON with both the typeof(Poco).Name and the Id used to form a unique key for that instance. E.g:

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

C# 客户端中的 POCO 通常使用 ServiceStack 的快速Json Serializer 进行序列化,其中只有具有公共 getter 的属性才会被序列化(以及公共 setter 来反序列化).

POCOs in the C# Client are conventionally serialized using ServiceStack's fast Json Serializer where only properties with public getters are serialized (and public setters to get de-serialized back).

默认值可使用 [DataMember] 属性覆盖,但不推荐使用,因为它会丑化您的 POCO.

Defaults are overrideable with [DataMember] attrs but not recommended since it uglifies your POCOs.

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

So knowing that POCOs in Redis are just blobbed, you only want to keep non-aggregate root data on your POCOs as public properties (unless you purposely want to store redundant data). A good convention is to use methods to fetch the related data (since it wont get serialized) but also tells your app which methods make remote calls to read data.

所以关于 Feed 是否应该与 User 一起存储的问题是它是否是非聚合根数据,即您是否想要访问用户在用户上下文之外馈送?如果没有,则离开 List;User 类型的 Feeds 属性.

So the question on whether the Feed should get stored with the User is whether or not it's non-aggregate root data, i.e. whether or not you want to access the users feeds outside the context of the user? If no, then leave the List<Feed> Feeds property on the User type.

然而,如果您希望保持所有提要可独立访问,即使用 redisFeeds.GetById(1),那么您将希望将其存储在用户之外并维护链接 2 个实体的索引.

If however you would like to keep all feeds accessible independently, i.e. with redisFeeds.GetById(1) then you will want to store it outside of the user and maintain an index linking the 2 entities.

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

As you've noticed there are many ways to store relationships between entities and how you do so is largely a matter of preference. For the child entity in a parent>child relationship you would always want to store the ParentId with the child entity. For the Parent you can either choose to store a collection of ChildIds with the model and then do a single fetch for all child entities to re-hydrate the model.

另一种方法是为每个父实例在其自己的Set中维护父dto之外的索引.一些很好的例子在 C# 源代码中Redis StackOverflow demo的,其中Users >问题用户>答案存储在:

Another way is to maintain the index outside of the parent dto in its own Set for each parent instance. Some good examples of this is in the C# Source code of the Redis StackOverflow demo where the relationship of Users > Questions and Users > Answers is stored in:

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

虽然 C# RedisClient 确实通过其 TParent.StoreRelatedEntities()TParent.GetRelatedEntities()TParent.DeleteRelatedEntities() API,其中一个索引在幕后维护,看起来像:

Although the C# RedisClient does include support for a default Parent/Child convention via its TParent.StoreRelatedEntities(), TParent.GetRelatedEntities<TChild>() and TParent.DeleteRelatedEntities() APIs where an index is maintained behind the scene that looks like:

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

实际上,这些只是您可能的一些选择,其中有许多不同的方法可以实现相同的目的,并且您也可以自由选择自己的方法.

Effectively these are just some of your possible options, where there are many different ways to achieve the same end and in which you also have the freedom to roll your own.

应该接受 NoSQL 的无模式、松散类型的自由,并且您不应该担心在使用 RDBMS 时尝试遵循您可能熟悉的严格的预定义结构.

NoSQL's schema-less, loose-typing freedoms should be embraced and you shouldn't be worried about trying to follow a rigid, pre-defined structure you might be familiar with when using an RDBMS.

总而言之,没有真正的正确方法在 Redis 中存储数据,例如C# Redis 客户端做出一些假设,以提供围绕 POCO 的高级 API,并将 POCO 写入 Redis 的二进制安全字符串值中 - 尽管有其他客户端更喜欢将实体属性存储在 Redis 哈希(字典)中.两者都可以.

In conclusion, there's no real right way to store data in Redis, e.g. The C# Redis Client makes some assumptions in order to provide a high-level API around POCOs and it blobs the POCOs in Redis's binary-safe string values - though there are other clients will prefer to store an entities properties in Redis Hashes (Dictionaries) instead. Both will work.

这篇关于ServiceStack.Net Redis:存储相关对象与相关对象 ID的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆