转:.NET中使用Redis (二)

原文来自于: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存储复杂对象有所帮助。

参考资料

  1. Designing NoSql Database
  2. Migrations Using Schemaless NoSql
  3. That No SQL Thing: The relational modeling anti pattern in document databases
时间: 2024-08-29 12:49:33

转:.NET中使用Redis (二)的相关文章

.NET中使用Redis (二)

很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据.本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象. 和传统的关系型数据库不同,NoSQL大部分都是以键值对存储在内存中的,我们不能直接把RDBMS里面的一些做法直接移植到NoSQL中来,一个最主要的原因是,在NoSQL中缺少RDBMS中的一些诸如join ,union以及一些在关系型数据库中效率很高的执行语句,这些在NoSQL不能很好的支持,或者说效率

C#中使用Redis学习二 .NET4.5中使用redis hash操作

上一篇>> 摘要 上一篇讲述了安装redis客户端和服务器端,也大体地介绍了一下redis.本篇着重讲解.NET4.0 和 .NET4.5中如何使用redis和C# redis操作哈希表.并且会将封装的一些代码贴一下.在讲解的过程中,我打算结合redis操作命令一起叙述,算是作为对比吧.这样也能让读者清楚了解,所分装的代码对应的redis的哪一些操作命令. hash哈希表简介 如何在.NET4.0/4.5中安装redis组件? 在上一篇博文中,安装好的redis服务器端,要记得开启服务.然后再

C#中使用Redis学习二 在.NET4.5中使用redis hash操作

C#中使用REDIS学习一 WINDOWS安装REDIS服务器端和客户端 http://www.tuicool.com/articles/uMb2Yjz 摘要 上一篇讲述了安装redis客户端和服务器端,也大体地介绍了一下redis.本篇着重讲解.NET4.0 和 .NET4.5中如何使用redis和C# redis操作哈希表.并且会将封装的一些代码贴一下.在讲解的过程中,我打算结合redis操作命令一起叙述,算是作为对比吧.这样也能让读者清楚了 解,所分装的代码对应的redis的哪一些操作命令

(二)如何在.net中使用Redis

Step1:使用NuGet工具安装Redis C# API,这里有多个API我们可以使用其中一个: 这样在代码中使用Redis需要使用到的Dll就自动引用进来啦: Step2: 也就是写代码了: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 using ServiceStack.Redis; 7 8 nam

在java中使用redis

在java中使用redis很简单,只需要添加jedist.jar,通过它的api就可以了.而且,api和redis的语法几乎完全相同.以下简单的测试: 参考:http://www.runoob.com/redis/redis-java.html 1 package com.test.redis; 2 3 import org.junit.Test; 4 import redis.clients.jedis.Jedis; 5 6 import java.util.HashMap; 7 import

Golang中使用log(二):Golang 标准库log的实现

前一篇文章我们看到了Golang标准库中log模块的使用,那么它是如何实现的呢?下面我从log.Logger开始逐步分析其实现. 其源码可以参考官方地址 1.Logger结构 首先来看下类型Logger的定义: type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix to write at beginning of each

.Net中初探Redis

一.简介 Redis是著名的NOSQL数据库,本质就是存储键值对结构的数据,为存储键值对数据做了优化,在大型网站中应用很多.Redis提供了数据的自动过期处理,因此适合存储临时数据. 和Redis类似的还有Memcached, Redis可以把数据持久化到硬盘中,而Memcached是放到内存中,重启后就消失,一般用Memcached做缓存. 二.Redis服务器的部署(Windows) Redis服务器有Linux.Windows版,Linux版性能好适合生产环境.这里只说明Windows里配

linux中的redis缓存服务器

Linux中的Redis缓存服务器 一.Redis基础部分: 1.redis介绍与安装比mysql快10倍以上 *****************redis适用场合**************** 1.取最新N个数据的操作 2.排行榜应用,取TOP N 操作 3.需要精确设定过期时间的应用 4.计数器应用 5.Uniq操作,获取某段时间所有数据排重值 6.实时系统,反垃圾系统7.Pub/Sub构建实时消息系统 7.Pub/Sub构建实时消息系统8.构建队列系统 9.缓存 ============

Django中使用Redis

django中使用redis 方案一:(在其它web框架也可以使用) utils文件夹下,建立redis_pool.py import redis POOL=redis.ConnectionPool('127.0.0.1',6379,max_connections=1000) 视图函数中使用: from django.shortcuts import render, HttpResponse import redis from app01.redis_pool import POOL # 这是通