天天看點

C#用戶端Redis伺服器的分布式緩存

介紹

在這篇文章中,我想介紹我知道的一種最緊湊的安裝和配置redis伺服器的方式。另外,我想簡短地概述一下在.net / c#用戶端下redis hash(哈希類型)和list(連結清單)的使用。

在這篇文章主要講到:

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的安裝

redis應用程式的完整檔案也可以從壓縮檔案(x64)得到。

當你擁有了全套的應用程式檔案(如下圖所示),

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#用戶端Redis伺服器的分布式緩存

請確定有足夠的權限啟動該服務。安裝完畢後,請檢查該服務是否建立成功,目前是否正在運作:

C#用戶端Redis伺服器的分布式緩存

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包,并進行安裝。

C#用戶端Redis伺服器的分布式緩存

直接從執行個體化用戶端使用<code>set</code>/<code>get</code>方法示例:

string host = "localhost"; 

string elementkey = "testkeyredis"; 

using (redisclient redisclient = new redisclient(host)) 

      if (redisclient.get&lt;string&gt;(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&lt;string&gt;("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; } 

     iredistypedclient&lt;phone&gt; phones = redisclient.as&lt;phone&gt;(); 

     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會話狀态

&lt;sessionstate timeout="1" mode="custom" 

customprovider="redissessionstateprovider" cookieless="false"&gt; 

      &lt;providers&gt; 

        &lt;add name="redissessionstateprovider" writeexceptionstoeventlog="false" 

        type="redisprovider.sessionprovider.customserviceprovider" 

        server="localhost" port="6379" password="pasword"&gt; 

      &lt;/add&gt; &lt;/providers&gt; 

&lt;/sessionstate&gt; 

注意,此密碼是可以選擇的,看伺服器是否需要認證。它必須被真實的值替換或删除,如果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(); 

//... 

結果:

C#用戶端Redis伺服器的分布式緩存

asp.net輸出緩存提供者,并且redis可以用類似的方式進行配置。

redis set(集合)和list(清單)

主要要注意的是,redis清單實作ilist&lt;t&gt;,而redis集合實作<code>icollection&lt;t&gt;</code>。下面來說說如何使用它們。

當需要區分相同類型的不同分類對象時,使用清單。例如,我們有“mostselling(熱銷手機)”和“oldcollection(回收手機)”兩個清單:

using (var redisclient = new redisclient(host)) 

    //create a 'strongly-typed' api that makes all redis value operations to apply against phones 

    iredistypedclient&lt;phone&gt; redis = redisclient.as&lt;phone&gt;(); 

    iredislist&lt;phone&gt; mostselling = redis.lists["urn:phones:mostselling"]; 

    iredislist&lt;phone&gt; 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", 

    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&lt;phone&gt; lgphones = mostselling.where(ph =&gt; ph.manufacturer == "lg"); 

    // find specific 

    phone singleelement = mostselling.firstordefault(ph =&gt; ph.id == 8); 

    //reset sequence and delete all lists 

    redis.setsequence(0); 

    redisclient.remove("urn:phones:mostselling"); 

    redisclient.remove("urn:phones:oldcollection"); 

當需要存儲相關的資料集和收集統計資訊,例如answer -&gt; queustion給答案或問題投票時,redis集合就非常好使。假設我們有很多的問題(queustion)和答案(answer ),需要将它們存儲在緩存中。使用redis,我們可以這麼做:

/// &lt;summary&gt; 

/// gets or sets the redis manager. the built-in ioc used with servicestack autowires this property. 

/// &lt;/summary&gt; 

iredisclientsmanager redismanager { get; set; } 

/// delete question by performing compensating actions to 

/// storequestion() to keep the datastore in a consistent state 

/// &lt;param name="questionid"&gt; 

public void deletequestion(long questionid) 

    using (var redis = redismanager.getclient()) 

        var redisquestions = redis.as&lt;question&gt;(); 

        var question = redisquestions.getbyid(questionid); 

        if (question == null) return; 

        //decrement score in tags list 

        question.tags.foreach(tag =&gt; redis.incrementiteminsortedset("urn:tags", tag, -1)); 

        //remove all related answers 

        redisquestions.deleterelatedentities&lt;answer&gt;(questionid); 

        //remove this question from user index 

        redis.removeitemfromset("urn:user&gt;q:" + question.userid, questionid.tostring()); 

        //remove tag =&gt; questions index for each tag 

        question.tags.foreach("urn:tags&gt;q:" + tag.tolower(), questionid.tostring())); 

        redisquestions.deletebyid(questionid); 

public void storequestion(question question) 

        if (question.tags == null) question.tags = new list&lt;string&gt;(); 

        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 =&gt; redis.incrementiteminsortedset("urn:tags", tag, 1)); 

        } 

        redisquestions.store(question); 

        redisquestions.addtorecentslist(question); 

        redis.additemtoset("urn:user&gt;q:" + question.userid, question.id.tostring()); 

        //usage of tags - populate tag =&gt; questions index for each tag 

        question.tags.foreach(tag =&gt; redis.additemtoset 

        ("urn:tags&gt;q:" + tag.tolower(), question.id.tostring())); 

/// delete answer by performing compensating actions to 

/// storeanswer() to keep the datastore in a consistent state 

/// &lt;param name="answerid"&gt; 

public void deleteanswer(long questionid, long answerid) 

        var answer = redis.as&lt;question&gt;().getrelatedentities&lt;answer&gt; 

        (questionid).firstordefault(x =&gt; x.id == answerid); 

        if (answer == null) return; 

        redis.as&lt;question&gt;().deleterelatedentity&lt;answer&gt;(questionid, answerid); 

        //remove user =&gt; answer index 

        redis.removeitemfromset("urn:user&gt;a:" + answer.userid, answerid.tostring()); 

public void storeanswer(answer answer) 

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

            answer.id = redis.as&lt;answer&gt;().getnextsequence(); 

            answer.createddate = datetime.utcnow; 

        //store as a 'related answer' to the parent question 

        redis.as&lt;question&gt;().storerelatedentities(answer.questionid, answer); 

        //populate user =&gt; answer index 

        redis.additemtoset("urn:user&gt;a:" + answer.userid, answer.id.tostring()); 

public list&lt;answer&gt; getanswersforquestion(long questionid) 

        return redis.as&lt;question&gt;().getrelatedentities&lt;answer&gt;(questionid); 

public void votequestionup(long userid, long questionid) 

    //populate question =&gt; user and user =&gt; question set indexes in a single transaction 

    redismanager.exectrans(trans =&gt; 

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

        trans.queuecommand(redis =&gt; 

        redis.additemtoset("urn:q&gt;user+:" + questionid, userid.tostring())); 

        redis.removeitemfromset("urn:q&gt;user-:" + questionid, userid.tostring())); 

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

        redis.additemtoset("urn:user&gt;q+:" + userid, questionid.tostring())); 

        redis.removeitemfromset("urn:user&gt;q-:" + userid, questionid.tostring())); 

    }); 

public void votequestiondown(long userid, long questionid) 

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

        redis.additemtoset("urn:q&gt;user-:" + questionid, userid.tostring())); 

        redis.removeitemfromset("urn:q&gt;user+:" + questionid, userid.tostring())); 

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

        redis.additemtoset"urn:user&gt;q-:" + userid, questionid.tostring())); 

        redis.removeitemfromset("urn:user&gt;q+:" + userid, questionid.tostring())); 

public void voteanswerup(long userid, long answerid) 

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

        redis.additemtoset("urn:a&gt;user+:" + answerid, userid.tostring())); 

        redis.removeitemfromset("urn:a&gt;user-:" + answerid, userid.tostring())); 

        redis.additemtoset("urn:user&gt;a+:" + userid, answerid.tostring())); 

        redis.removeitemfromset("urn:user&gt;a-:" + userid, answerid.tostring())); 

public void voteanswerdown(long userid, long answerid) 

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

        redis.additemtoset("urn:a&gt;user-:" + answerid, userid.tostring())); 

        redis.removeitemfromset("urn:a&gt;user+:" + answerid, userid.tostring())); 

        redis.additemtoset("urn:user&gt;a-:" + userid, answerid.tostring())); 

        redis.removeitemfromset("urn:user&gt;a+:" + userid, answerid.tostring())); 

public questionresult getquestion(long questionid) 

    var question = redismanager.execas&lt;question&gt; 

    (redisquestions =&gt; redisquestions.getbyid(questionid)); 

    if (question == null) return null; 

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

    var answers = getanswersforquestion(questionid); 

    var uniqueuserids = answers.convertall(x =&gt; x.userid).tohashset(); 

    var usersmap = getusersbyids(uniqueuserids).todictionary(x =&gt; x.id); 

    result.answers = answers.convertall(answer =&gt; 

        new answerresult { answer = answer, user = usersmap[answer.userid] }); 

    return result; 

public list&lt;user&gt; getusersbyids(ienumerable&lt;long&gt; userids) 

    return redismanager.execas&lt;user&gt;(redisusers =&gt; redisusers.getbyids(userids)).tolist(); 

public questionstat getquestionstats(long questionid) 

    using (var redis = redismanager.getreadonlyclient()) 

        var result = new questionstat 

            votesupcount = redis.getsetcount("urn:q&gt;user+:" +questionid), 

            votesdowncount = redis.getsetcount("urn:q&gt;user-:" + questionid) 

        result.votestotal = result.votesupcount - result.votesdowncount; 

        return result; 

public list&lt;tag&gt; gettagsbypopularity(int skip, int take) 

        var tagentries = redis.getrangewithscoresfromsortedsetdesc("urn:tags", skip, take); 

        var tags = tagentries.convertall(kvp =&gt; new tag { name = kvp.key, score = (int)kvp.value }); 

        return tags; 

public sitestats getsitestats() 

        return new sitestats 

            questionscount = redis.as&lt;question&gt;().typeidsset.count, 

            answerscount = redis.as&lt;answer&gt;().typeidsset.count, 

            toptags = gettagsbypopularity(0, 10) 

附加資源說明

項目中引用的一些包在packages.config檔案中配置。

funq ioc的相關配置,以及注冊類型和目前控制器目錄,在global.asax檔案中配置。

基于ioc的緩存使用以及global.asax可以打開以下url:http://localhost:37447/question/getquestions?tag=test 檢視。

你可以将tag字段設定成test3,test1,test2等。

redis緩存配置——在web config檔案(&lt;system.web&gt;&lt;sessionstate&gt;節點)以及redissessionstateprovider.cs檔案中。

在mvc項目中有很多待辦事項,是以,如果你想改進/繼續,請更新,并上傳。

如果有人能提供使用redis(以及funq ioc)緩存的mvc應用程式示例,本人将不勝感激。funq ioc已經配置,使用示例已經在question controller中。

注:部分取樣于“servicestack.examples-master”解決方案。

結論。優化應用程式緩存以及快速本地緩存

由于redis并不在本地存儲(也不在本地複制)資料,那麼通過在本地緩存區存儲一些輕量級或使用者依賴的對象(跳過序列化字元串和用戶端—服務端資料轉換)來優化性能是有意義的。例如,在web應用中,對于輕量級的對象使用’<code>system.runtime.caching.objectcache</code>‘ 會更好——使用者依賴,并且應用程式時常要用。否則,當經常性地需要使用該對象時,就必須在分布式redis緩存中存儲大量容積的内容。使用者依賴的對象舉例——個人資料資訊,個性化資訊 。常用對象——本地化資料,不同使用者之間的共享資訊,等等。

作者:小峰

來源:51cto

繼續閱讀