`
近乎sns
  • 浏览: 12431 次
  • 性别: Icon_minigender_2
  • 来自: 青岛
文章分类
社区版块
存档分类
最新评论

.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存储复杂对象有所帮助。

 一些.net程序代码可下载近乎sns进行学习,研究

参考资料

  1. Designing NoSql Database

  2. Migrations Using Schemaless NoSql

  3. That No SQL Thing: The relational modeling anti pattern in document databases

分享到:
评论

相关推荐

    .NET中添加Redis

    在.NET环境中,通过使用客户端库,我们可以轻松地与Redis服务器进行交互,实现数据的高速读写和高效的缓存管理。 1. **安装Redis客户端库** 在.NET项目中使用Redis,首先需要安装一个合适的客户端库。...

    【ASP.NET编程知识】.net core使用redis基于StackExchange.Redis.docx

    ASP.NET Core 使用 Redis 基于 StackExchange.Redis ASP.NET Core 是一个开源的、跨平台的框架,使用 C# 语言开发。Redis 是一个基于内存的数据存储系统,可以用来存储和处理大量数据。StackExchange.Redis 是一个...

    在ASP.NET MVC中使用Redis 的Demo:通过Redis实现用户登陆,并保持登陆状态。

    在登录时,可以将用户ID和相关凭证序列化为JSON,然后存储到Redis中,例如: ```csharp public ActionResult Login(string username, string password) { UserModel user = Authenticate(username, password); // ...

    使用.Net 技术操作Redis缓存技术,对Redis缓存进行存储增删改查等相关操作

    在.NET开发环境中,我们通常使用StackExchange.Redis库来与Redis进行交互。本篇文章将详细探讨如何使用.NET技术,尤其是C#语言,来操作Redis进行存储、增删改查等操作。 首先,我们需要在项目中引入StackExchange....

    c#.net客户端与redis集群

    最全的教程部署redis cluster方式集群,及c# 写的访问集群的源码,自己的下载分数快不够,赚2分,教程地址:http://www.cnblogs.com/uucode/p/6486395.html

    在ASP.NET MVC中使用StackExCahnge.Redis实现用户登陆,并保持登陆

    在ASP.NET MVC项目中,我们可以创建一个自定义的SessionStateStoreProvider,将用户的登录信息(如SessionID、用户名、权限等)存储到Redis中。StackExchange.Redis库提供了方便的API来连接到Redis服务器,执行命令...

    【ASP.NET编程知识】详解如何在ASP.NET Core中使用Redis.docx

    接下来,初始化数据库和一个列表,这里我们使用了一个名为“MessageList”的List,方便演示如何在Redis中操作列表数据: ```csharp public static string ListKeyName = "MessageList"; public HomeController() {...

    .NET MVC Redis 实现简单的抢购队列

    在Redis中,`LPUSH`和`RPOP`命令常被用来构建先进先出(FIFO)队列。`RPush`用于从列表的右侧(尾部)添加元素,而`LPop`则用于从左侧(头部)移除并返回第一个元素。在抢购场景下,我们可以将每个用户的抢购请求视...

    .NET中使用Redis

    很久以前写了一篇文章.NET中使用Redis介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据。本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象。和传统的关系型数据库不同,NoSQL大部分都是...

    Redis VB.NET第一部分:在.NET中使用Redis(NoSQL)的简介

    在"Redis VB.NET第一部分:在.NET中使用Redis(NoSQL)的简介"中,我们将探讨如何在VB.NET环境中集成和使用Redis。这包括以下几个关键知识点: 1. **安装Redis**: 首先,你需要在本地或服务器上安装Redis。可以访问...

    详解Asp.net Core 使用Redis存储Session

    在`RedisCache`类中,有一个重要的Lua脚本`SetScript`,它是用来在Redis中设置Session数据的。Lua脚本允许我们在服务器端执行原子操作,确保数据的一致性。`SetScript`将Session数据以哈希表的形式存储,并根据需要...

    Windows-64位下安装Redis到asp.net开发

    Windows-64位下安装Redis到asp.net开发 一、Windows 64位下安装Redis 1. 下载Redis安装包 在Redis官方网站...使用Redis在asp.net开发中可以实现高效、可靠的缓存机制,提高应用程序的性能和可靠性。

    Asp.net 封装Redis帮助类

    在Asp.net项目中,可以将这个`RedisHelper`类注入到依赖注入容器中,这样在需要缓存服务的地方,就可以通过构造函数注入的方式获得`ICacheStorageProvider`实例,从而使用Redis缓存。 此外,考虑到`NoSqlRedis`这个...

    .NET下Redis入门演示

    在.NET环境中,我们可以使用StackExchange.Redis库来与Redis进行交互。本文将详细介绍如何在.NET下进行Redis的入门操作,以及如何通过提供的博客链接进行学习。 首先,安装StackExchange.Redis库是开始.NET与Redis...

    .net core使用redis基于StackExchange.Redis

    在了解.NET Core使用Redis的实践操作中,我们首先需要明白的是.NET Core和Redis的关系,以及如何利用.NET Core中强大的包管理器NuGet来引入并使用StackExchange.Redis这个库来实现与Redis的交互。StackExchange....

    .NET下Redis操作类

    - **获取值**:`Item_Get&lt;T&gt;` 方法用于从Redis中获取指定键的值。 - **删除键**:`Item_Remove` 方法用于从Redis中删除指定的键。 - **列表操作**:`List_Add&lt;T&gt;` 和 `List_Remove&lt;T&gt;` 分别用于向列表中添加元素和从...

Global site tag (gtag.js) - Google Analytics