标签:
原文来自于:http://blog.jobbole.com/83824/
很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据。本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象。
和传统的关系型数据库不同,NoSQL大部分都是以键值对存储在内存中的,我们不能直接把RDBMS里面的一些做法直接移植到NoSQL中来,一个最主要的原因是,在NoSQL中缺少RDBMS中的一些诸如join ,union以及一些在关系型数据库中效率很高的执行语句,这些在NoSQL不能很好的支持,或者说效率低。
下文首先通过例子介绍在SQLServer中设计一个DB系统以及与NoSQL环境中设计一个DB的区别,最后演示如何在Redis中对数据进行读写操作。
假设我们要设计一个简单的博客系统,用户可以注册一个博客(Blog),然后可以在上面写文章(Post),文章可以分类(Category)以及添加标签(Tag),用户可以对文章进行评论(Comment)。
在该系统中,我们需要实现,如下基本功能:
如果在SQLServer中,相信很简单就可以设计出这样一个DB了。
在NoSQL环境中,我们不能直接将上面的结构搬进来,所以需要根据需求重新设计我们的模型。
在NoSQL环境下,所有的数据其实都是以key和value的形式存储在内存中的,value通常是序列化为字符串保存的。我们使用redis客户端的时候,可以直接将对象存储,这些客户端在内部实现上帮助我们进行了序列化。所以第一步就是需要定义实体模型:
首先来看User实体:
1
2
3
4
5
6
7
8
9
10
11
|
public class User { public User() { this .BlogIds = new List< long >(); } public long Id { get ; set ; } public string Name { get ; set ; } public List< long > BlogIds { get ; set ; } } |
User实体中,包含了用户的Id,Name以及博客的Id。
然后Blog实体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Blog { public Blog() { this .Tags = new List< string >(); this .BlogPostIds = new List< long >(); } public long Id { get ; set ; } public long UserId { get ; set ; } public string UserName { get ; set ; } public List< string > Tags { get ; set ; } public List< long > BlogPostIds { get ; set ; } } |
包含了标签Tag,以及文章Id列表。
文章BolgPost实体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class BlogPost { public BlogPost() { this .Categories = new List< string >(); this .Tags = new List< string >(); this .Comments = new List<BlogPostComment>(); } public long Id { get ; set ; } public long BlogId { get ; set ; } public string Title { get ; set ; } public string Content { get ; set ; } public List< string > Categories { get ; set ; } public List< string > Tags { get ; set ; } public List<BlogPostComment> Comments { get ; set ; } } |
包含了一篇文章的基本信息,如文章分类,文章标签,文章的评论。
最后看评论BlogPostComment实体:
1
2
3
4
5
|
public class BlogPostComment { public string Content { get ; set ; } public DateTime CreatedDate { get ; set ; } } |
实体定义好了之后,我们就可以开始具体实现了。为了演示,这里通过单元测试的方式实现具体功能:
首先要把Redis的服务端启动起来,然后在工程中新建一个Redis客户端,之后的所有操作都通过这个客户端进行。
1
2
3
4
5
6
7
8
9
10
11
12
|
[TestFixture, Explicit, Category( "Integration" )] public class BlogPostExample { readonly RedisClient redis = new RedisClient( "localhost" ); [SetUp] public void OnBeforeEachTest() { redis.FlushAll(); InsertTestData(); } } |
在单元测试的SetUp中,我们插入一些模拟数据,插入数据的方法为InsetTestData方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
public void InsertTestData() { var redisUsers = redis.As<User>(); var redisBlogs = redis.As<Blog>(); var redisBlogPosts = redis.As<BlogPost>(); var yangUser = new User { Id = redisUsers.GetNextSequence(), Name = "Eric Yang" }; var zhangUser = new User { Id = redisUsers.GetNextSequence(), Name = "Fish Zhang" }; var yangBlog = new Blog { Id = redisBlogs.GetNextSequence(), UserId = yangUser.Id, UserName = yangUser.Name, Tags = new List< string > { "Architecture" , ".NET" , "Databases" }, }; var zhangBlog = new Blog { Id = redisBlogs.GetNextSequence(), UserId = zhangUser.Id, UserName = zhangUser.Name, Tags = new List< string > { "Architecture" , ".NET" , "Databases" }, }; var blogPosts = new List<BlogPost> { new BlogPost { Id = redisBlogPosts.GetNextSequence(), BlogId = yangBlog.Id, Title = "Memcache" , Categories = new List< string > { "NoSQL" , "DocumentDB" }, Tags = new List< string > { "Memcache" , "NoSQL" , "JSON" , ".NET" } , Comments = new List<BlogPostComment> { new BlogPostComment { Content = "First Comment!" , CreatedDate = DateTime.UtcNow,}, new BlogPostComment { Content = "Second Comment!" , CreatedDate = DateTime.UtcNow,}, } }, new BlogPost { Id = redisBlogPosts.GetNextSequence(), BlogId = zhangBlog.Id, Title = "Redis" , Categories = new List< string > { "NoSQL" , "Cache" }, Tags = new List< string > { "Redis" , "NoSQL" , "Scalability" , "Performance" }, Comments = new List<BlogPostComment> { new BlogPostComment { Content = "First Comment!" , CreatedDate = DateTime.UtcNow,} } }, new BlogPost { Id = redisBlogPosts.GetNextSequence(), BlogId = yangBlog.Id, Title = "Cassandra" , Categories = new List< string > { "NoSQL" , "Cluster" }, Tags = new List< string > { "Cassandra" , "NoSQL" , "Scalability" , "Hashing" }, Comments = new List<BlogPostComment> { new BlogPostComment { Content = "First Comment!" , CreatedDate = DateTime.UtcNow,} } }, new BlogPost { Id = redisBlogPosts.GetNextSequence(), BlogId = zhangBlog.Id, Title = "Couch Db" , Categories = new List< string > { "NoSQL" , "DocumentDB" }, Tags = new List< string > { "CouchDb" , "NoSQL" , "JSON" }, Comments = new List<BlogPostComment> { new BlogPostComment {Content = "First Comment!" , CreatedDate = DateTime.UtcNow,} } }, }; yangUser.BlogIds.Add(yangBlog.Id); yangBlog.BlogPostIds.AddRange(blogPosts.Where(x => x.BlogId == yangBlog.Id).Map(x => x.Id)); zhangUser.BlogIds.Add(zhangBlog.Id); zhangBlog.BlogPostIds.AddRange(blogPosts.Where(x => x.BlogId == zhangBlog.Id).Map(x => x.Id)); redisUsers.Store(yangUser); redisUsers.Store(zhangUser); redisBlogs.StoreAll( new [] { yangBlog, zhangBlog }); redisBlogPosts.StoreAll(blogPosts); } |
在方法中,首先在Redis中创建了三个强类型的IRedisTypedClient类型的对象redisUsers,redisBlogs,redisBlogPosts来保存用户信息,博客信息,和文字信息。
1
|
var yangUser = new User { Id = redisUsers.GetNextSequence(), Name = "Eric Yang" }; |
在新建用户的时候,因为Id是自增字段,所以直接调用redisUsers这个client的GetNextSequence()方法就可以获得一个自增的Id。
创建完用户之后,接着创建博客信息:
1
2
3
4
5
6
7
|
var yangBlog = new Blog { Id = redisBlogs.GetNextSequence(), UserId = yangUser.Id, UserName = yangUser.Name, Tags = new List< string > { "Architecture" , ".NET" , "Databases" }, }; |
该博客有几个标签。
在接着创建该博客上发表的若干篇文章:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var blogPosts = new List<BlogPost> { new BlogPost { Id = redisBlogPosts.GetNextSequence(), BlogId = yangBlog.Id, Title = "Memcache" , Categories = new List< string > { "NoSQL" , "DocumentDB" }, Tags = new List< string > { "Memcache" , "NoSQL" , "JSON" , ".NET" } , Comments = new List<BlogPostComment> { new BlogPostComment { Content = "First Comment!" , CreatedDate = DateTime.UtcNow,}, new BlogPostComment { Content = "Second Comment!" , CreatedDate = DateTime.UtcNow,}, } } } |
每一篇文章都有分类和标签,以及评论。
然后需要给user的BlogsIds和blog的BlogPostIds赋值
1
2
|
yangUser.BlogIds.Add(yangBlog.Id); yangBlog.BlogPostIds.AddRange(blogPosts.Where(x => x.BlogId == yangBlog.Id).Map(x => x.Id)); |
最后需要把这些信息保存到redis中。
1
2
3
4
5
6
7
|
//保存用户信息 redisUsers.Store(yangUser); redisUsers.Store(zhangUser); //保存博客信息 redisBlogs.StoreAll( new [] { yangBlog, zhangBlog }); //保存所有的文章信息 redisBlogPosts.StoreAll(blogPosts); |
现在,利用Redis Desktop Manager,可以查看Reidis中存储的数据:
数据准备好了之后,可以实现前面列出的一系列方法了:
该方法在GetAllBlogs中,实现如下:
1
2
3
4
5
6
7
|
[Test] public void Show_a_list_of_blogs() { var redisBlogs = redis.As<Blog>(); var blogs = redisBlogs.GetAll(); blogs.PrintDump(); } |
只需要调用GetAll方法即可获取内存中的所有指定类型的对象。
输出结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
[ { Id: 1, UserId: 1, UserName: Eric Yang, Tags: [ Architecture, .NET, Databases ], BlogPostIds: [ 1, 3 ] }, { Id: 2, UserId: 2, UserName: Fish Zhang, Tags: [ Architecture, .NET, Databases ], BlogPostIds: [ 2, 4 ] } ] |
实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
[Test] public void Show_a_list_of_recent_posts_and_comments() { //Get strongly-typed clients var redisBlogPosts = redis.As<BlogPost>(); var redisComments = redis.As<BlogPostComment>(); { //To keep this example let‘s pretend this is a new list of blog posts var newIncomingBlogPosts = redisBlogPosts.GetAll(); //Let‘s get back an IList<BlogPost> wrapper around a Redis server-side List. var recentPosts = redisBlogPosts.Lists[ "urn:BlogPost:RecentPosts" ]; var recentComments = redisComments.Lists[ "urn:BlogPostComment:RecentComments" ]; foreach (var newBlogPost in newIncomingBlogPosts) { //Prepend the new blog posts to the start of the ‘RecentPosts‘ list recentPosts.Prepend(newBlogPost); //Prepend all the new blog post comments to the start of the ‘RecentComments‘ list newBlogPost.Comments.ForEach(recentComments.Prepend); } //Make this a Rolling list by only keep the latest 3 posts and comments recentPosts.Trim(0, 2); recentComments.Trim(0, 2); //Print out the last 3 posts: recentPosts.GetAll().PrintDump(); recentComments.GetAll().PrintDump(); } } |
方法中定义了两个key为urn:BlogPost:RecentPosts 和 urn:BlogPostComment:RecentComments的 List对象来保存最近发表的文章和评论:recentPosts.Prepend(newBlogPost)方法表示将新创建的文章插到recentPosts列表的最前面。
Trim方法表示仅保留n个在集合中。
显示博客的标签云方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
[Test] public void Show_a_TagCloud() { //Get strongly-typed clients var redisBlogPosts = redis.As<BlogPost>(); var newIncomingBlogPosts = redisBlogPosts.GetAll(); foreach (var newBlogPost in newIncomingBlogPosts) { //For every tag in each new blog post, increment the number of times each Tag has occurred newBlogPost.Tags.ForEach(x => redis.IncrementItemInSortedSet( "urn:TagCloud" , x, 1)); } //Show top 5 most popular tags with their scores var tagCloud = redis.GetRangeWithScoresFromSortedSetDesc( "urn:TagCloud" , 0, 4); tagCloud.PrintDump(); } |
显示标签云的实现,用到了redis中的SortedSet,IncrementItemInSortedSet表示如果有相同的话,值加一,GetRangeWithScoresFromSortedSetDesc方法,获取某一key的前5个对象。
显示所有的分类用到了Set对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[Test] public void Show_all_Categories() { var redisBlogPosts = redis.As<BlogPost>(); var blogPosts = redisBlogPosts.GetAll(); foreach (var blogPost in blogPosts) { blogPost.Categories.ForEach(x => redis.AddItemToSet( "urn:Categories" , x)); } var uniqueCategories = redis.GetAllItemsFromSet( "urn:Categories" ); uniqueCategories.PrintDump(); } |
实现如下:
1
2
3
4
5
6
7
8
9
10
11
|
[Test] public void Show_post_and_all_comments() { //There is nothing special required here as since comments are Key Value Objects //they are stored and retrieved with the post var postId = 1; var redisBlogPosts = redis.As<BlogPost>(); var selectedBlogPost = redisBlogPosts.GetById(postId.ToString()); selectedBlogPost.PrintDump(); } |
只需要把postId传进去就可以通过GetById的方法获取内存中的对象.
首先根据PostId获取BlogPost,然后在Comment属性中添加一个BlogPostComment对象,然后在保存改BlogPost.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
[Test] public void Add_comment_to_existing_post() { var postId = 1; var redisBlogPosts = redis.As<BlogPost>(); var blogPost = redisBlogPosts.GetById(postId.ToString()); blogPost.Comments.Add( new BlogPostComment { Content = "Third Post!" , CreatedDate = DateTime.UtcNow }); redisBlogPosts.Store(blogPost); var refreshBlogPost = redisBlogPosts.GetById(postId.ToString()); refreshBlogPost.PrintDump(); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[Test] public void Show_all_Posts_for_the_DocumentDB_Category() { var redisBlogPosts = redis.As<BlogPost>(); var newIncomingBlogPosts = redisBlogPosts.GetAll(); foreach (var newBlogPost in newIncomingBlogPosts) { //For each post add it‘s Id into each of it‘s ‘Cateogry > Posts‘ index newBlogPost.Categories.ForEach(x => redis.AddItemToSet( "urn:Category:" + x, newBlogPost.Id.ToString())); } //Retrieve all the post ids for the category you want to view var documentDbPostIds = redis.GetAllItemsFromSet( "urn:Category:DocumentDB" ); //Make a batch call to retrieve all the posts containing the matching ids //(i.e. the DocumentDB Category posts) var documentDbPosts = redisBlogPosts.GetByIds(documentDbPostIds); documentDbPosts.PrintDump(); } |
这里首先把所有的文章按照标签新建Set,把相同的分类的文章放到一个Set中,最后根据key即可查找到相应的集合。
本文利用一个简单的博客系统,简要介绍了如何利用Redis存储和获取复杂的数据。由于本文主要为了演示如何与Redis进行交互,所以实体设计的很简陋,没有按照DDD的思想进行设计,在某些设计方面没有遵循前文浅谈依赖注入中使用的原理和方法,后面会写文章对该系统进行重构以使之更加完善。
希望本文对您了解如何利用Redis存储复杂对象有所帮助。
参考资料
标签:
原文地址:http://www.cnblogs.com/guoyongrong/p/4259174.html