C#程序设计:C#客户端Redis服务器的分布式缓存
安安 2017-08-15 来源 :网络 阅读 924 评论 0

摘要:本篇C#程序设计教程将为大家讲解C#程序设计的知识点,看完这篇文章会让你对C#编程的知识点有更加清晰的理解和运用。

本篇C#程序设计教程将为大家讲解C#程序设计的知识点,看完这篇文章会让你对C#编程的知识点有更加清晰的理解和运用。

介绍

在这篇文章中,我想介绍我知道的一种最紧凑的安装和配置Redis服务器的方式。另外,我想简短地概述一下在.NET / C#客户端下Redis hash(哈希类型)和list(链表)的使用。

在这篇文章主要讲到:

· 安装Redis服务器(附完整的应用程序文件设置)

· Redis服务器保护(配置身份验证)

· 配置服务器复制

· 从C#应用程序访问缓存

· 使用Redis ASP.NET会话状态

· Redis 集合(Set)、列表(List)和事务处理用法示例

· 说明附加的源(Redis Funq LoC MVC项目:举例)

· 缓存的优化思路

背景

Redis是最快也是功能最丰富的内存Key-Value数据存储系统之一。

缺点

· 没有本地数据缓存(如在Azure缓存同步本地数据缓存)

· 没有完全集群化的支持(不过,可能今年年底会实现)

优点

· 易于配置

· 使用简单

· 高性能

· 支持不同的数据类型(如hash(哈希类型)、list(链表)、set(集合)、sorted set(有序集))

· ASP.NET会话集成

· Web UI用于浏览缓存内容

下面我将简单说明如何在服务器上安装和配置Redis,并用C#使用它。

Redis的安装

从https://github.com/dmajkic/redis/downloads(win32 win64直接链接)下载二进制文件,解包档案到应用程序目录(如C:\Program Files\Redis)

下载从https://github.com/kcherenkov/redis-windows-service/downloads编译的Redis服务,然后复制到程序文件夹(如C:\Program Files\Redis)。如果配置文件丢失,也可以下载复制到应用程序目录。有效的Redis配置文件的范例在https://raw.github.com/antirez/redis/2.6/redis.conf。

Redis应用程序的完整文件也可以从压缩文件(x64)得到。

当你拥有了全套的应用程序文件(如下图所示),

 C#程序设计:C#客户端Redis服务器的分布式缓存

导航到应用程序目录,然后运行以下命令:

sc create %name% binpath= "\"%binpath%\" %configpath%" start= "auto" DisplayName= "Redis"

其中:

· %name%——服务实例的名称,例如:redis-instance;

· %binpath%——到项目exe文件的路径,例如:C:\Program Files\Redis\RedisService_1.1.exe;

· %configpath%——到Redis配置文件的路径,例如:C:\Program Files\Redis\redis.conf;

举例:

sc create Redis start= auto DisplayName= Redis binpath= "\"C:\Program Files\Redis\RedisService_1.1.exe\

" \"C:\Program Files\Redis\redis.conf\""

即应该是这样的:

 C#程序设计:C#客户端Redis服务器的分布式缓存

请确保有足够的权限启动该服务。安装完毕后,请检查该服务是否创建成功,当前是否正在运行:

 C#程序设计:C#客户端Redis服务器的分布式缓存

或者,你可以使用安装程序(我没试过):https://github.com/rgl/redis/downloads。

Redis服务器保护:密码,IP过滤

保护Redis服务器的主要方式是使用Windows防火墙或活跃的网络连接属性设置IP过滤。此外,还可以使用Redis密码设置额外保护。这需要用下面的方式更新Redis配置文件(redis.conf):

首先,找到这行:

# requirepass foobared

删除开头的#符号,用新密码替换foobared:

requirepass foobared

然后,重新启动Redis Windows服务!

当具体使用客户端的时候,使用带密码的构造函数:

RedisClient client = new RedisClient(serverHost, port, redisPassword);

Redis服务器复制(主—从配置)

Redis支持主从同步,即,每次主服务器修改,从服务器得到通知,并自动同步。大多复制用于读取(但不能写)扩展和数据冗余和服务器故障转移。设置两个Redis实例(在相同或不同服务器上的两个服务),然后配置其中之一作为从站。为了让Redis服务器实例是另一台服务器的从属,可以这样更改配置文件:

找到以下代码:

# slaveof

替换为:

slaveof 192.168.1.1 6379

(可以自定义指定主服务器的真实IP和端口)。如果主服务器配置为需要密码(验证),可以如下所示改变redis.conf,找到这一行代码:

# masterauth

删除开头的#符号,用主服务器的密码替换,即:

masterauth mastpassword

现在这个Redis实例可以被用来作为主服务器的只读同步副本。

用C#代码使用Redis缓存

用C#代码使用Redis运行Manage NuGet包插件,找到ServiceStack.Redis包,并进行安装。

 C#程序设计:C#客户端Redis服务器的分布式缓存

直接从实例化客户端使用Set/Get方法示例:

string host = "localhost";string elementKey = "testKeyRedis";

using (RedisClient redisClient = new RedisClient(host))

{

      if (redisClient.Get(elementKey) == null)

      {

           // adding delay to see the difference

           Thread.Sleep(5000);

           // save value in cache

           redisClient.Set(elementKey, "some cached value");

      }

      // get value from the cache by key

      message = "Item value is: " + redisClient.Get("some cached value");

 }

类型化实体集更有意思和更实用,这是因为它们操作的是确切类型的对象。在下面的代码示例中,有两个类分别定义为Phone和Person——phone的主人。每个phone实例引用它的主人。下面的代码演示我们如何通过标准添加、删除和发现缓存项:

public class Phone

{

   public int Id { get; set; }

   public string Model { get; set; }

   public string Manufacturer { get; set; }

   public Person Owner { get; set; }

}

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }

    public int Age { get; set; }

    public string Profession { get; set; }

}

using (RedisClient redisClient = new RedisClient(host))

{

     IRedisTypedClientphones = redisClient.As();

     Phone phoneFive = phones.GetValue("5");

     if (phoneFive == null)

     {

          // make a small delay

          Thread.Sleep(5000);

          // creating a new Phone entry

          phoneFive = new Phone

          {

               Id = 5,

               Manufacturer = "Motorolla",

               Model = "xxxxx",

               Owner = new Person

               {

                    Id = 1,

                    Age = 90,

                    Name = "OldOne",

                    Profession = "sportsmen",

                    Surname = "OldManSurname"

               }

          };

          // adding Entry to the typed entity set

          phones.SetEntry(phoneFive.Id.ToString(), phoneFive);

     }

     message = "Phone model is " + phoneFive.Manufacturer;

     message += "Phone Owner Name is: " + phoneFive.Owner.Name;

}

在上面的例子中,我们实例化了输入端IRedisTypedClient,它与缓存对象的特定类型——Phone类型一起工作。

Redis ASP.NET会话状态

要用Redis提供商配置ASP.NET会话状态,添加新文件到你的Web项目,命名为RedisSessionStateProvider.cs,可以从https://github.com/chadman/redis-service-provider/raw/master/RedisProvider/SessionProvider/RedisSessionProvider.cs复制代码,然后添加或更改配置文件中的以下部分(sessionState标签已经内置于system.web标签),或者你也可以下载附加来源和复制代码。

      

        <add name="RedisSessionStateProvider" writeexceptionstoeventlog="false" 

        type="RedisProvider.SessionProvider.CustomServiceProvider" 

        server="localhost" port="6379" password="pasword">

       

注意,此密码是可以选择的,看服务器是否需要认证。它必须被真实的值替换或删除,如果Redis服务器不需要身份验证,那么服务器属性和端口得由具体的数值代替(默认端口为6379)。然后在项目中,你才可以使用会话状态:

// in the Global.asaxpublic class MvcApplication1 : System.Web.HttpApplication

{

    protected void Application_Start()

    {

        //....

    }

 

    protected void Session_Start()

    {

        Session["testRedisSession"] = "Message from the redis ression";

    }

}

在Home controller(主控制器):

public class HomeController : Controller

{

    public ActionResult Index()

    {

       //...

       ViewBag.Message = Session["testRedisSession"];

       return View();

    }//...

}

结果:

 C#程序设计:C#客户端Redis服务器的分布式缓存

ASP.NET输出缓存提供者,并且Redis可以用类似的方式进行配置。

Redis Set(集合)和List(列表)

主要要注意的是,Redis列表实现IList,而Redis集合实现ICollection。下面来说说如何使用它们。

当需要区分相同类型的不同分类对象时,使用列表。例如,我们有“mostSelling(热销手机)”和“oldCollection(回收手机)”两个列表:

string host = "localhost";

using (var redisClient = new RedisClient(host))

{

    //Create a 'strongly-typed' API that makes all Redis Value operations to apply against Phones

    IRedisTypedClientredis = redisClient.As();

 

    IRedisListmostSelling = redis.Lists["urn:phones:mostselling"];

    IRedisListoldCollection = redis.Lists["urn:phones:oldcollection"];

 

    Person phonesOwner = new Person

        {

            Id = 7,

            Age = 90,

            Name = "OldOne",

            Profession = "sportsmen",

            Surname = "OldManSurname"

        };

 

    // adding new items to the list

    mostSelling.Add(new Phone

            {

                Id = 5,

                Manufacturer = "Sony",

                Model = "768564564566",

                Owner = phonesOwner

            });

 

    oldCollection.Add(new Phone

            {

                Id = 8,

                Manufacturer = "Motorolla",

                Model = "324557546754",

                Owner = phonesOwner

            });

 

    var upgradedPhone  = new Phone

    {

        Id = 3,

        Manufacturer = "LG",

        Model = "634563456",

        Owner = phonesOwner

    };

 

    mostSelling.Add(upgradedPhone);

 

    // remove item from the list

    oldCollection.Remove(upgradedPhone);

 

    // find objects in the cache

    IEnumerableLGPhones = mostSelling.Where(ph => ph.Manufacturer == "LG");

 

    // find specific

    Phone singleElement = mostSelling.FirstOrDefault(ph => ph.Id == 8);

 

    //reset sequence and delete all lists

    redis.SetSequence(0);

    redisClient.Remove("urn:phones:mostselling");

    redisClient.Remove("urn:phones:oldcollection");

}

当需要存储相关的数据集和收集统计信息,例如answer -> queustion给答案或问题投票时,Redis集合就非常好使。假设我们有很多的问题(queustion)和答案(answer ),需要将它们存储在缓存中。使用Redis,我们可以这么做:

/// 

/// Gets or sets the Redis Manager. The built-in IoC used with ServiceStack autowires this property./// 

IRedisClientsManager RedisManager { get; set; }/// 

/// Delete question by performing compensating actions to /// StoreQuestion() to keep the datastore in a consistent state/// 

/// 

public void DeleteQuestion(long questionId){

    using (var redis = RedisManager.GetClient())

    {

        var redisQuestions = redis.As();

 

        var question = redisQuestions.GetById(questionId);

        if (question == null) return;

 

        //decrement score in tags list

        question.Tags.ForEach(tag => redis.IncrementItemInSortedSet("urn:tags", tag, -1));

 

        //remove all related answers

        redisQuestions.DeleteRelatedEntities(questionId);

 

        //remove this question from user index

        redis.RemoveItemFromSet("urn:user>q:" + question.UserId, questionId.ToString());

 

        //remove tag => questions index for each tag

        question.Tags.ForEach("urn:tags>q:" + tag.ToLower(), questionId.ToString()));

 

        redisQuestions.DeleteById(questionId);

    }

}

public void StoreQuestion(Question question){

    using (var redis = RedisManager.GetClient())

    {

        var redisQuestions = redis.As();

 

        if (question.Tags == null) question.Tags = new List();

        if (question.Id == default(long))

        {

            question.Id = redisQuestions.GetNextSequence();

            question.CreatedDate = DateTime.UtcNow;

 

            //Increment the popularity for each new question tag

            question.Tags.ForEach(tag => redis.IncrementItemInSortedSet("urn:tags", tag, 1));

        }

 

        redisQuestions.Store(question);

        redisQuestions.AddToRecentsList(question);

        redis.AddItemToSet("urn:user>q:" + question.UserId, question.Id.ToString());

 

        //Usage of tags - Populate tag => questions index for each tag

        question.Tags.ForEach(tag => redis.AddItemToSet

        ("urn:tags>q:" + tag.ToLower(), question.Id.ToString()));

    }

}

/// 

/// Delete Answer by performing compensating actions to /// StoreAnswer() to keep the datastore in a consistent state/// 

/// 

/// 

public void DeleteAnswer(long questionId, long answerId){

    using (var redis = RedisManager.GetClient())

    {

        var answer = redis.As().GetRelatedEntities

        (questionId).FirstOrDefault(x => x.Id == answerId);

        if (answer == null) return;

 

        redis.As().DeleteRelatedEntity(questionId, answerId);

 

        //remove user => answer index

        redis.RemoveItemFromSet("urn:user>a:" + answer.UserId, answerId.ToString());

    }

}

public void StoreAnswer(Answer answer){

    using (var redis = RedisManager.GetClient())

    {

        if (answer.Id == default(long))

        {

            answer.Id = redis.As().GetNextSequence();

            answer.CreatedDate = DateTime.UtcNow;

        }

 

        //Store as a 'Related Answer' to the parent Question

        redis.As().StoreRelatedEntities(answer.QuestionId, answer);

        //Populate user => answer index

        redis.AddItemToSet("urn:user>a:" + answer.UserId, answer.Id.ToString());

    }

}

public ListGetAnswersForQuestion(long questionId){

    using (var redis = RedisManager.GetClient())

    {

        return redis.As().GetRelatedEntities(questionId);

    }

}

public void VoteQuestionUp(long userId, long questionId){

    //Populate Question => User and User => Question set indexes in a single transaction

    RedisManager.ExecTrans(trans =>

    {

        //Register upvote against question and remove any downvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet("urn:q>user+:" + questionId, userId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:q>user-:" + questionId, userId.ToString()));

 

        //Register upvote against user and remove any downvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet("urn:user>q+:" + userId, questionId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:user>q-:" + userId, questionId.ToString()));

    });

}

public void VoteQuestionDown(long userId, long questionId){

    //Populate Question => User and User => Question set indexes in a single transaction

    RedisManager.ExecTrans(trans =>

    {

        //Register downvote against question and remove any upvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet("urn:q>user-:" + questionId, userId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:q>user+:" + questionId, userId.ToString()));

 

        //Register downvote against user and remove any upvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet"urn:user>q-:" + userId, questionId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:user>q+:" + userId, questionId.ToString()));

    });

}

public void VoteAnswerUp(long userId, long answerId){

    //Populate Question => User and User => Question set indexes in a single transaction

    RedisManager.ExecTrans(trans =>

    {

        //Register upvote against answer and remove any downvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet("urn:a>user+:" + answerId, userId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:a>user-:" + answerId, userId.ToString()));

 

        //Register upvote against user and remove any downvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet("urn:user>a+:" + userId, answerId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:user>a-:" + userId, answerId.ToString()));

    });

}

public void VoteAnswerDown(long userId, long answerId){

    //Populate Question => User and User => Question set indexes in a single transaction

    RedisManager.ExecTrans(trans =>

    {

        //Register downvote against answer and remove any upvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet("urn:a>user-:" + answerId, userId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:a>user+:" + answerId, userId.ToString()));

 

        //Register downvote against user and remove any upvotes if any

        trans.QueueCommand(redis =>

        redis.AddItemToSet("urn:user>a-:" + userId, answerId.ToString()));

        trans.QueueCommand(redis =>

        redis.RemoveItemFromSet("urn:user>a+:" + userId, answerId.ToString()));

    });

}

public QuestionResult GetQuestion(long questionId){

    var question = RedisManager.ExecAs

    (redisQuestions => redisQuestions.GetById(questionId));

    if (question == null) return null;

 

    var result = ToQuestionResults(new[] { question })[0];

    var answers = GetAnswersForQuestion(questionId);

    var uniqueUserIds = answers.ConvertAll(x => x.UserId).ToHashSet();

    var usersMap = GetUsersByIds(uniqueUserIds).ToDictionary(x => x.Id);

 

    result.Answers = answers.ConvertAll(answer =>

        new AnswerResult { Answer = answer, User = usersMap[answer.UserId] });

 

    return result;

}

public ListGetUsersByIds(IEnumerableuserIds){

    return RedisManager.ExecAs(redisUsers => redisUsers.GetByIds(userIds)).ToList();

}

public QuestionStat GetQuestionStats(long questionId){

    using (var redis = RedisManager.GetReadOnlyClient())

    {

        var result = new QuestionStat

        {

            VotesUpCount = redis.GetSetCount("urn:q>user+:" +questionId),

            VotesDownCount = redis.GetSetCount("urn:q>user-:" + questionId)

        };

        result.VotesTotal = result.VotesUpCount - result.VotesDownCount;

        return result;

    }

}

public ListGetTagsByPopularity(int skip, int take){

    using (var redis = RedisManager.GetReadOnlyClient())

    {

        var tagEntries = redis.GetRangeWithScoresFromSortedSetDesc("urn:tags", skip, take);

        var tags = tagEntries.ConvertAll(kvp => new Tag { Name = kvp.Key, Score = (int)kvp.Value });

        return tags;

    }

}

public SiteStats GetSiteStats(){

    using (var redis = RedisManager.GetClient())

    {

        return new SiteStats

        {

            QuestionsCount = redis.As().TypeIdsSet.Count,

            AnswersCount = redis.As().TypeIdsSet.Count,

            TopTags = GetTagsByPopularity(0, 10)

        };

    }

}

附加资源说明

项目中引用的一些包在packages.config文件中配置。

Funq IoC的相关配置,以及注册类型和当前控制器目录,在Global.asax文件中配置。

基于IoC的缓存使用以及Global.asax可以打开以下URL://localhost:37447/Question/GetQuestions?tag=test 查看。

你可以将tag字段设置成test3,test1,test2等。

Redis缓存配置——在web config文件(节点)以及RedisSessionStateProvider.cs文件中。

在MVC项目中有很多待办事项,因此,如果你想改进/继续,请更新,并上传。

如果有人能提供使用Redis(以及Funq IOC)缓存的MVC应用程序示例,本人将不胜感激。Funq IOC已经配置,使用示例已经在Question controller中。

注:部分取样于“ServiceStack.Examples-master”解决方案。

结论。优化应用程序缓存以及快速本地缓存

由于Redis并不在本地存储(也不在本地复制)数据,那么通过在本地缓存区存储一些轻量级或用户依赖的对象(跳过序列化字符串和客户端—服务端数据转换)来优化性能是有意义的。例如,在Web应用中,对于轻量级的对象使用’System.Runtime.Caching.ObjectCache‘ 会更好——用户依赖,并且应用程序时常要用。否则,当经常性地需要使用该对象时,就必须在分布式Redis缓存中存储大量容积的内容。用户依赖的对象举例——个人资料信息,个性化信息 。常用对象——本地化数据,不同用户之间的共享信息,等等。

下载源代码(Redis Funq LoC MVC 4版本)


许可证

这篇文章,以及任何相关的源代码和文件,依据The Code Project Open License (CPOL)。


希望这篇文章可以帮助到你。总之,同学们,你想要的职坐标C#频道都能找到!

本文由 @安安 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved