介紹
在這篇文章中,我想介紹我知道的一種最緊湊的安裝和配置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)得到。
當你擁有了全套的應用程式檔案(如下圖所示),
導航到應用程式目錄,然後運作以下指令:
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\""
即應該是這樣的:
請確定有足夠的權限啟動該服務。安裝完畢後,請檢查該服務是否建立成功,目前是否正在運作:
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包,并進行安裝。
直接從執行個體化用戶端使用<code>set</code>/<code>get</code>方法示例:
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; }
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會話狀态
<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集合實作<code>icollection<t></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<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",
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; }
/// delete question by performing compensating actions to
/// storequestion() to keep the datastore in a consistent state
/// <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)
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()));
/// delete answer by performing compensating actions to
/// storeanswer() to keep the datastore in a consistent state
/// <param name="answerid">
public void deleteanswer(long questionid, long answerid)
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)
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)
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()));
redis.removeitemfromset("urn:q>user-:" + questionid, userid.tostring()));
//register upvote against user and remove any downvotes if any
redis.additemtoset("urn:user>q+:" + userid, questionid.tostring()));
redis.removeitemfromset("urn:user>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>user-:" + questionid, userid.tostring()));
redis.removeitemfromset("urn:q>user+:" + questionid, userid.tostring()));
//register downvote against user and remove any upvotes if any
redis.additemtoset"urn:user>q-:" + userid, questionid.tostring()));
redis.removeitemfromset("urn:user>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>user+:" + answerid, userid.tostring()));
redis.removeitemfromset("urn:a>user-:" + answerid, userid.tostring()));
redis.additemtoset("urn:user>a+:" + userid, answerid.tostring()));
redis.removeitemfromset("urn:user>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>user-:" + answerid, userid.tostring()));
redis.removeitemfromset("urn:a>user+:" + answerid, userid.tostring()));
redis.additemtoset("urn:user>a-:" + userid, answerid.tostring()));
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)
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()
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://localhost:37447/question/getquestions?tag=test 檢視。
你可以将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應用中,對于輕量級的對象使用’<code>system.runtime.caching.objectcache</code>‘ 會更好——使用者依賴,并且應用程式時常要用。否則,當經常性地需要使用該對象時,就必須在分布式redis緩存中存儲大量容積的内容。使用者依賴的對象舉例——個人資料資訊,個性化資訊 。常用對象——本地化資料,不同使用者之間的共享資訊,等等。
作者:小峰
來源:51cto