介绍
在这篇文章中,我想介绍我知道的一种最紧凑的安装和配置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://gith
下载从https://gith
Redis应用程序的完整文件也可以从压缩文件(x64)得到。
当你拥有了全套的应用程序文件(如下图所示),
导航到应用程序目录,然后运行以下命令:
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\""
即应该是这样的:
请确保有足够的权限启动该服务。安装完毕后,请检查该服务是否创建成功,当前是否正在运行:
或者,你可以使用安装程序(我没试过):https://gith
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 <masterip> <masterport>
替换为:
slaveof 192.168.1.1 6379
(可以自定义指定主服务器的真实IP和端口)。如果主服务器配置为需要密码(验证),可以如下所示改变redis.conf,找到这一行代码:
# masterauth <master-password>
删除开头的#符号,用主服务器的密码替换<master-password>,即:
masterauth mastpassword
现在这个Redis实例可以被用来作为主服务器的只读同步副本。
用C#代码使用Redis缓存
用C#代码使用Redis运行Manage NuGet包插件,找到ServiceStack.Redis包,并进行安装。
直接从实例化客户端使用Set
/Get
方法示例:
string host = "localhost";
string elementKey = "testKeyRedis";
using (RedisClient redisClient = new RedisClient(host))
{
if (redisClient.Get<string>(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<string>("some cached value");
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
类型化实体集更有意思和更实用,这是因为它们操作的是确切类型的对象。在下面的代码示例中,有两个类分别定义为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))
{
IRedisTypedClient<phone> phones = redisClient.As<phone>();
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;
}
- 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.
在上面的例子中,我们实例化了输入端IRedisTypedClient,它与缓存对象的特定类型——Phone类型一起工作。
Redis ASP.NET会话状态
要用Redis提供商配置ASP.NET会话状态,添加新文件到你的Web项目,命名为RedisSessionStateProvider.cs,可以从https://gith
<sessionstate timeout="1" mode="Custom"
customprovider="RedisSessionStateProvider" cookieless="false">
<providers>
<add name="RedisSessionStateProvider" writeexceptionstoeventlog="false"
type="RedisProvider.SessionProvider.CustomServiceProvider"
server="localhost" port="6379" password="pasword">
</add> </providers>
</sessionstate>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
注意,此密码是可以选择的,看服务器是否需要认证。它必须被真实的值替换或删除,如果Redis服务器不需要身份验证,那么服务器属性和端口得由具体的数值代替(默认端口为6379)。然后在项目中,你才可以使用会话状态:
// in the Global.asax
public 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();
}
//...
}
- 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.
结果:
ASP.NET输出缓存提供者,并且Redis可以用类似的方式进行配置。
Redis Set(集合)和List(列表)
主要要注意的是,Redis列表实现IList<T>,而Redis集合实现ICollection<T>
。下面来说说如何使用它们。
当需要区分相同类型的不同分类对象时,使用列表。例如,我们有“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
IRedisTypedClient<phone> redis = redisClient.As<phone>();
IRedisList<phone> mostSelling = redis.Lists["urn:phones:mostselling"];
IRedisList<phone> oldCollection = 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
IEnumerable<phone> LGPhones = 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");
}
- 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.
当需要存储相关的数据集和收集统计信息,例如answer -> queustion给答案或问题投票时,Redis集合就非常好使。假设我们有很多的问题(queustion)和答案(answer ),需要将它们存储在缓存中。使用Redis,我们可以这么做:
/// <summary>
/// Gets or sets the Redis Manager. The built-in IoC used with ServiceStack autowires this property.
/// </summary>
IRedisClientsManager RedisManager { get; set; }
/// <summary>
/// Delete question by performing compensating actions to
/// StoreQuestion() to keep the datastore in a consistent state
/// </summary>
/// <param name="questionId">
public void DeleteQuestion(long questionId)
{
using (var redis = RedisManager.GetClient())
{
var redisQuestions = redis.As<question>();
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<answer>(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<question>();
if (question.Tags == null) question.Tags = new List<string>();
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()));
}
}
/// <summary>
/// Delete Answer by performing compensating actions to
/// StoreAnswer() to keep the datastore in a consistent state
/// </summary>
/// <param name="questionId">
/// <param name="answerId">
public void DeleteAnswer(long questionId, long answerId)
{
using (var redis = RedisManager.GetClient())
{
var answer = redis.As<question>().GetRelatedEntities<answer>
(questionId).FirstOrDefault(x => x.Id == answerId);
if (answer == null) return;
redis.As<question>().DeleteRelatedEntity<answer>(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<answer>().GetNextSequence();
answer.CreatedDate = DateTime.UtcNow;
}
//Store as a 'Related Answer' to the parent Question
redis.As<question>().StoreRelatedEntities(answer.QuestionId, answer);
//Populate user => answer index
redis.AddItemToSet("urn:user>a:" + answer.UserId, answer.Id.ToString());
}
}
public List<answer> GetAnswersForQuestion(long questionId)
{
using (var redis = RedisManager.GetClient())
{
return redis.As<question>().GetRelatedEntities<answer>(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<question>
(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 List<user> GetUsersByIds(IEnumerable<long> userIds)
{
return RedisManager.ExecAs<user>(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 List<tag> GetTagsByPopularity(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<question>().TypeIdsSet.Count,
AnswersCount = redis.As<answer>().TypeIdsSet.Count,
TopTags = GetTagsByPopularity(0, 10)
};
}
}
- 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.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
附加资源说明
项目中引用的一些包在packages.config文件中配置。
Funq IoC的相关配置,以及注册类型和当前控制器目录,在Global.asax文件中配置。
基于IoC的缓存使用以及Global.asax可以打开以下URL:http://local
你可以将tag字段设置成test3,test1,test2等。
Redis缓存配置——在web config文件(<system.web><sessionState>节点)以及RedisSessionStateProvider.cs文件中。
在MVC项目中有很多待办事项,因此,如果你想改进/继续,请更新,并上传。
如果有人能提供使用Redis(以及Funq IOC)缓存的MVC应用程序示例,本人将不胜感激。Funq IOC已经配置,使用示例已经在Question controller中。
注:部分取样于“ServiceStack.Examples-master”解决方案。
结论。优化应用程序缓存以及快速本地缓存
由于Redis并不在本地存储(也不在本地复制)数据,那么通过在本地缓存区存储一些轻量级或用户依赖的对象(跳过序列化字符串和客户端—服务端数据转换)来优化性能是有意义的。例如,在Web应用中,对于轻量级的对象使用’System.Runtime.Caching.ObjectCache
‘ 会更好——用户依赖,并且应用程序时常要用。否则,当经常性地需要使用该对象时,就必须在分布式Redis缓存中存储大量容积的内容。用户依赖的对象举例——个人资料信息,个性化信息 。常用对象——本地化数据,不同用户之间的共享信息,等等。
链接
如何运行Redis服务:
https://gith
文档:
.NET / C#示例:
https://gith
关于如何用C#在Windows上使用Redis的好建议:
http://maxiv
http://www.p
关于Redis:
https://gith
Azure缓存
http://kotug
许可证
这篇文章,以及任何相关的源代码和文件,依据The Code Project Open License (CPOL)。
译文链接:http://www.codeceo.com/article/distributed-caching-redis-server.html
英文原文:Distributed Caching using Redis Server with .NET/C# Client