Pages

Thứ Bảy, 19 tháng 12, 2015

Hướng dẫn sử dụng redis caching với .NET C

Redis giúp quản lý cache, tăng tốc độ truy xuất cho website. Đây là công nghệ không thể thiếu đối với các trang web có lưu lượng truy cập lớn.



1. Redis là gì

Redis là một cơ sở dữ liệu nguồn mở lưu trữ theo dạng key-value.  Toàn bộ dữ liệu của Redis được lưu trên bộ nhớ nên tốc độ xử lý cực kỳ nhanh, thậm chí có phần vượt trên memcached. Theo định kỳ dữ liệu từ trên bộ nhớ sẽ được đẩy xuống ổ đĩa.

2. Cài đặt Redis server

- Download tại: https://github.com/dmajkic/redis/downloads  và giải nén C:\Program Files\Redis
- Download Redis service tại https://github.com/kcherenkov/redis-windows-service/downloads, và copy tới C:\Program Files\Redis
Khi đó bạn sẽ có các file như sau:
redis application folder conten
Chạy command để bắt đầu cài đặt:

sc create Redis start= auto DisplayName= Redis binpath= "\"C:\Program Files\Redis\RedisService_1.1.exe\ " \"C:\Program Files\Redis\redis.conf\"" 
Installing redis as windows service 
Nếu quá trình cài đặt thành công sẽ xuất hiện service redis:
redis running as a windows service
3. Cầu hình redis server
- Đặt password, IP filtering: Mở file config redis redis.conf
Tìm tới dòng 
# requirepass foobared   

Remove ký tự # và thay foobared bằng password mới

Kết quả: 

requirepass foobared

Khởi động lại Redis Windows service
Khi đó từ client truy xuất sẽ cần password: 
RedisClient client = new RedisClient(serverHost, port, redisPassword);

- Config Redis Server Replication (master-slave):

Redis cho phép bạn phân tán cache trên nhiều server với nhiều chế độ: read/write hoặc read nhưng không write,..

Để thực hiện bạn tìm đến dòng:

# slaveof <masterip> <masterport>
Thay thế bằng thông tin server slave. VD: 
slaveof 192.168.1.1 6379

Với server master bạn có thể đặt password tại dòng: 

# masterauth <master-password>

VD:  masterauth mastpassword

Quá trình cài đặt và config hoàn tất. Giờ bạn có thể bắt đầu sử dụng chúng từ các ứng dụng.

4. Truy xuất redis cache từ C#

- Download và add thư viện ServiceStack.Redis từ NuGet :
Ví dụ minh họa Get và set dữ liệu:

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");  }

5. ASP.NET Session State with Redis
Để cấu hình ASP.NET session state sử dụng redis thì cần add thêm file RedisSessionStateProvider.cs vào project (download tại  https://github.com/chadman/redis-service-provider/raw/master/RedisProvider/SessionProvider/RedisSessionProvider.cs)

Sau đó cầu hình trong web.config:

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

6. Redis Sets and Lists

Ví dụ minh họa:

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             });      mostSelling.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"); }

--------------------------

/// <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)         };     } }

Chúc các bạn thành công!