C#客戶端Redis伺服器的分散式快取
本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!
介紹
在這篇文章中,我想介紹我知道的一種最緊湊的安裝和配置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"); }
型別化實體集更有意思和更實用,這是因為它們操作的是確切型別的物件。在下面的程式碼示例中,有兩個類分別定義為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; }
在上面的例子中,我們例項化了輸入端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>
注意,此密碼是可以選擇的,看伺服器是否需要認證。它必須被真實的值替換或刪除,如果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(); } //... }
結果:
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"); }
當需要儲存相關的資料集和收集統計資訊,例如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) }; } }
附加資源說明
專案中引用的一些包在packages.config檔案中配置。
Funq IoC的相關配置,以及註冊型別和當前控制器目錄,在Global.asax檔案中配置。
基於IoC的快取使用以及Global.asax可以開啟以下URL:http://local
你可以將tag欄位設定成test3,test1,test2等。
Redis快取配置——在web config檔案(<system.web><sessionState>節點)以及RedisSessionStateProvider.cs檔案中。
如果有人能提供使用Redis(以及Funq IOC)快取的MVC應用程式示例,本人將不勝感激。Funq IOC已經配置,使用示例已經在Question controller中。
注:部分取樣於“ServiceStack.Examples-master”解決方案。
結論。優化應用程式快取以及快速本地快取
由於Redis並不在本地儲存(也不在本地複製)資料,那麼通過在本地快取區儲存一些輕量級或使用者依賴的物件(跳過序列化字串和客戶端—服務端資料轉換)來優化效能是有意義的。例如,在Web應用中,對於輕量級的物件使用’System.Runtime.Caching.ObjectCache
‘ 會更好——使用者依賴,並且應用程式時常要用。否則,當經常性地需要使用該物件時,就必須在分散式Redis快取中儲存大量容積的內容。使用者依賴的物件舉例——個人資料資訊,個性化資訊 。常用物件——本地化資料,不同使用者之間的共享資訊,等等。
連結
如何執行Redis服務:
https://gith
文件:
http://redis
.NET / C#示例:
https://gith
關於如何用C#在Windows上使用Redis的好建議:
http://www.p
關於Redis:
https://gith
Azure快取
許可證
這篇文章,以及任何相關的原始碼和檔案,依據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
翻譯作者:碼農網 – 小峰
[ 轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]
相關文章
- Redis 6.0 客戶端快取的伺服器端實現Redis客戶端快取伺服器
- redis→分散式快取Redis分散式快取
- SmartSql Redis 分散式快取SQLRedis分散式快取
- 分散式配置中心客戶端分散式客戶端
- c# 獲取客戶端IPC#客戶端
- c#實現redis客戶端(一)C#Redis客戶端
- Redis——快取穿透、快取擊穿、快取雪崩、分散式鎖Redis快取穿透分散式
- 實踐篇 -- Redis客戶端快取在SpringBoot應用的探究Redis客戶端快取Spring Boot
- Redis分散式快取安裝和使用Redis分散式快取
- 如何用REDIS實現分散式快取Redis分散式快取
- redis客戶端的使用Redis客戶端
- Redis從客戶端登入伺服器Redis客戶端伺服器
- [Redis 客戶端整合] Java 中常用Redis客戶端比較Redis客戶端Java
- Redis-客戶端Redis客戶端
- redis客戶端管理Redis客戶端
- 億級流量客戶端快取之Http快取與本地快取對比客戶端快取HTTP
- Redis原始碼剖析——客戶端和伺服器Redis原始碼客戶端伺服器
- redis伺服器/客戶端安裝與配置Redis伺服器客戶端
- windows bilibili客戶端快取影片匯出Windows客戶端快取
- WEB 應用快取解析以及使用 Redis 實現分散式快取Web快取Redis分散式
- 分散式快取分散式快取
- C# MQTT客戶端C#MQQT客戶端
- 分散式快取伺服器Memcached介紹分散式快取伺服器
- Redis客戶端連線Redis客戶端
- Redis C客戶端APIRedis客戶端API
- 伺服器獲取真實客戶端 IP伺服器客戶端
- MySQL:MySQL客戶端快取結果導致OOMMySql客戶端快取OOM
- 在 WPF 客戶端實現 AOP 和介面快取客戶端快取
- day03-Redis的客戶端Redis客戶端
- 基於Dtm分散式事務管理的php客戶端分散式PHP客戶端
- 分散式快取方案分散式快取
- 聊聊分散式快取分散式快取
- 聊聊本地快取和分散式快取快取分散式
- EhCache 分散式快取/快取叢集分散式快取
- Redis介紹 && Java客戶端操作RedisRedisJava客戶端
- redis:常用客戶端命令(redis-cli)Redis客戶端
- 初探 Redis 客戶端 Lettuce:真香!Redis客戶端
- 【Silverlight】利用IsolatedStorageFile實現客戶端快取客戶端快取