.NET中使用Redis (二)

很久以前寫了一篇文章 .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了。

Blog database in SQLServer

在NoSQL環境中,我們不能直接將上面的結構搬進來,所以需要根據需求重新設計我們的模型。

定義實體

在NoSQL環境下,所有的數據其實都是以key和value的形式存儲在內存中的,value通常是序列化爲字符串保存的。我們使用redis客戶端的時候,可以直接將對象存儲,這些客戶端在內部實現上幫助我們進行了序列化。所以第一步就是需要定義實體模型:

首先來看User實體:

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實體:

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實體:

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實體:

public class BlogPostComment
{
    public string Content { get; set; }
    public DateTime CreatedDate { get; set; }
}

 class diagrame in Redis

具體實現

實體定義好了之後,我們就可以開始具體實現了。爲了演示,這裏通過單元測試的方式實現具體功能:

首先要把Redis的服務端啓動起來,然後在工程中新建一個Redis客戶端,之後的所有操作都通過這個客戶端進行。

[TestFixture, Explicit, Category("Integration")]
public class BlogPostExample
{
    readonly RedisClient redis = new RedisClient("localhost");

    [SetUp]
    public void OnBeforeEachTest()
    {
        redis.FlushAll();
        InsertTestData();
    }
}

在單元測試的SetUp中,我們插入一些模擬數據,插入數據的方法爲InsetTestData方法:

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來保存用戶信息,博客信息,和文字信息。

var yangUser = new User { Id = redisUsers.GetNextSequence(), Name = "Eric Yang" };

在新建用戶的時候,因爲Id是自增字段,所以直接調用redisUsers這個client的GetNextSequence()方法就可以獲得一個自增的Id。

創建完用戶之後,接着創建博客信息:

var yangBlog = new Blog
{
    Id = redisBlogs.GetNextSequence(),
    UserId = yangUser.Id,
    UserName = yangUser.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,},
        }
    }
}

每一篇文章都有分類和標籤,以及評論。

然後需要給user的BlogsIds和blog的BlogPostIds賦值

yangUser.BlogIds.Add(yangBlog.Id);
yangBlog.BlogPostIds.AddRange(blogPosts.Where(x => x.BlogId == yangBlog.Id).Map(x => x.Id));

最後需要把這些信息保存到redis中。

//保存用戶信息 
redisUsers.Store(yangUser);
redisUsers.Store(zhangUser);
//保存博客信息
redisBlogs.StoreAll(new[] { yangBlog, zhangBlog });
//保存所有的文章信息
redisBlogPosts.StoreAll(blogPosts);

現在,利用Redis Desktop Manager,可以查看Reidis中存儲的數據:

PostSystemDB

數據準備好了之後,可以實現前面列出的一系列方法了:

顯示所有博客

該方法在GetAllBlogs中,實現如下:

[Test]
public void Show_a_list_of_blogs()
{
    var redisBlogs = redis.As<Blog>();
    var blogs = redisBlogs.GetAll();
    blogs.PrintDump();
}

只需要調用GetAll方法即可獲取內存中的所有指定類型的對象。

輸出結果爲:

[
    {
        
        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
        ]
    }
]

顯示最近發表的文章和評論

實現如下:

[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個在集合中。

顯示博客的標籤雲

顯示博客的標籤雲方法如下:

[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對象。

[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();
}

顯示文章以及其評論

實現如下:

[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.

[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();
}

顯示分類以及分類對應的文章

[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/yangecnu/p/Introduct-Redis-in-DotNET-Part2.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章