作者:13
GitHub:https://github.com/ZHENFENG13
版權(quán)聲明:本文為原創(chuàng)文章,未經(jīng)允許不得轉(zhuǎn)載。
簡(jiǎn)介
這是一篇關(guān)于Redis使用的總結(jié)類型文章,會(huì)先簡(jiǎn)單的談一下緩存的應(yīng)用場(chǎng)景、緩存的使用邏輯及注意事項(xiàng),然后是Redis緩存與數(shù)據(jù)庫(kù)間結(jié)合以進(jìn)行系統(tǒng)優(yōu)化,當(dāng)然文章的最后也會(huì)給出具體的代碼實(shí)現(xiàn),不至于看到文章的你一頭霧水,理論要講,項(xiàng)目代碼也要分享,這是我寫(xiě)博客的基本出發(fā)點(diǎn)。

應(yīng)用場(chǎng)景
Redis能做什么呢?
這是個(gè)好問(wèn)題,不同的人可能會(huì)給出不同的答案,因?yàn)樗膽?yīng)用場(chǎng)景真的很多,作為一個(gè)優(yōu)秀的nosql數(shù)據(jù)庫(kù)可以結(jié)合其他產(chǎn)品做很多事情,比如:tomcat集群的session同步、與nginx和lua結(jié)合做限流工具、基于Redis的分布式鎖實(shí)現(xiàn)、分布式系統(tǒng)唯一主鍵生成策略、秒殺場(chǎng)景中也會(huì)看到它、它還能夠作為一個(gè)消息隊(duì)列.....
Redis的應(yīng)用場(chǎng)景很多很多,以上也只是列舉了一部分而已,由于本文是圍繞我的開(kāi)源項(xiàng)目perfect-ssm來(lái)寫(xiě)的,所以在本文的場(chǎng)景就是一個(gè)緩存中間層,對(duì)于讀多寫(xiě)少的應(yīng)用場(chǎng)景,我們經(jīng)常使用緩存來(lái)進(jìn)行優(yōu)化以提高系統(tǒng)性能。
我曾經(jīng)寫(xiě)過(guò)一篇《一次線上Mysql數(shù)據(jù)庫(kù)崩潰事故的記錄》的文章,里面記錄了Web請(qǐng)求是如何毫不留情的摧垮mysql數(shù)據(jù)庫(kù),進(jìn)而導(dǎo)致網(wǎng)站應(yīng)用無(wú)法正常運(yùn)轉(zhuǎn)。當(dāng)時(shí)的情況就是數(shù)據(jù)庫(kù)讀請(qǐng)求太多,事故的主要原因也是這個(gè),后續(xù)的解決方案也就是在項(xiàng)目中添加緩存層,使得熱點(diǎn)數(shù)據(jù)得以存入緩存,不會(huì)重復(fù)的去讀取mysql,將大部分請(qǐng)求壓力轉(zhuǎn)移至Redis緩存中以減輕mysql的負(fù)擔(dān)。

接入緩存后的處理邏輯
請(qǐng)求過(guò)來(lái)后,首先判斷Redis里面有沒(méi)有,有數(shù)據(jù)則直接返回Redis中的數(shù)據(jù)給用戶,沒(méi)有則查詢數(shù)據(jù)庫(kù),如果數(shù)據(jù)庫(kù)中也沒(méi)有則返回空或者提醒語(yǔ)句即可。
當(dāng)然,針對(duì)不同的操作,對(duì)于Redis和mysql的操作也是不同的:
添加操作
如果是需要放入緩存的數(shù)據(jù),那么在向mysql數(shù)據(jù)庫(kù)中插入成功后,生成對(duì)應(yīng)的key至,并存入Redis中。
修改操作
向mysql數(shù)據(jù)庫(kù)中修改成功后,修改Redis中的數(shù)據(jù),但是Redis并沒(méi)有更新語(yǔ)句,所以只能先刪除,再添加完成更新操作。
需要注意的是,考慮到程序?qū)τ赗edis的操作可能會(huì)失敗,這時(shí)mysql中的數(shù)據(jù)已經(jīng)修改,但是Redis中的數(shù)據(jù)依然是上一次的數(shù)據(jù),導(dǎo)致數(shù)據(jù)不一致的問(wèn)題,所以是先操作Redis還是先操作mysql需要慎重考慮。
刪除操作
與修改操作相同,先刪除數(shù)據(jù),再更新緩存,但是同樣會(huì)有出現(xiàn)數(shù)據(jù)不一致問(wèn)題的可能性需要注意,如果數(shù)據(jù)庫(kù)中的數(shù)據(jù)刪除了,但是Redis中的數(shù)據(jù)沒(méi)刪除,又會(huì)出現(xiàn)業(yè)務(wù)問(wèn)題。
查詢操作
首先通過(guò)Redis查詢,如果緩存中已經(jīng)存在數(shù)據(jù)則直接返回即可,此時(shí)就不再需要通過(guò)mysql數(shù)據(jù)庫(kù)來(lái)獲取數(shù)據(jù),減少對(duì)mysql的請(qǐng)求,如果緩存中不存在數(shù)據(jù),則依然通過(guò)mysql數(shù)據(jù)庫(kù)查詢,查詢到數(shù)據(jù)后,存入Redis緩存中。
本項(xiàng)目中的代碼是先操作mysql,再操作Redis,有概率會(huì)出現(xiàn)上文中提到的數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)不一致的情況,所以需要注意,本文的代碼只做參考,用到實(shí)際項(xiàng)目中還是需要根據(jù)具體的業(yè)務(wù)邏輯進(jìn)行合理的修改。
使用緩存的建議
緩存存儲(chǔ)策略:
可以緩存的數(shù)據(jù)的特征基本上是以下幾點(diǎn):
- 熱點(diǎn)數(shù)據(jù)
- 實(shí)時(shí)性要求不高的數(shù)據(jù)
- 業(yè)務(wù)邏輯簡(jiǎn)單的數(shù)據(jù)
至于什么數(shù)據(jù),不同的系統(tǒng)、不同的項(xiàng)目要求肯定不同,這里不做過(guò)多討論,只簡(jiǎn)單的說(shuō)一下自己的想法,結(jié)合以上的特征總結(jié)如下:
- 1.首頁(yè)數(shù)據(jù)、分類數(shù)據(jù)這些數(shù)據(jù)屬于熱點(diǎn)數(shù)據(jù),首頁(yè)數(shù)據(jù)更是熱得發(fā)燙,而且這類數(shù)據(jù)一般實(shí)時(shí)性不高,不會(huì)頻繁的去操作,比較適合放入緩存。
- 2.詳情數(shù)據(jù),比如文章詳情、商品詳情、廣告詳情、個(gè)人信息詳情,這些數(shù)據(jù)庫(kù)中單條的的數(shù)據(jù)可以以其id生成不同的key保存到Redis,操作比較簡(jiǎn)單明了,在更新或者刪除的時(shí)候需要同步更新Redis中的數(shù)據(jù),這類數(shù)據(jù)也適合放入緩存中。
- 3.列表數(shù)據(jù)不是特別推薦,除非是實(shí)時(shí)性和改變頻率真的很低的情況下,因?yàn)榱斜硗鶢可娴臄?shù)據(jù)和操作很多,處理起來(lái)比較復(fù)雜,如果對(duì)實(shí)時(shí)性要求低的話、或者部分字段更新頻率低的話,可以換成這部分?jǐn)?shù)據(jù)。
緩存存儲(chǔ)策略的制定說(shuō)難也難,說(shuō)容易也容易,主要是根據(jù)具體的業(yè)務(wù)場(chǎng)景合理的操作即可,以上只是做了一個(gè)簡(jiǎn)單的總結(jié)。
緩存失效策略:
失效策略一定要做好,血的教訓(xùn)。
- 定時(shí)刪除
含義:在設(shè)置key的過(guò)期時(shí)間的同時(shí),為該key創(chuàng)建一個(gè)定時(shí)器,讓定時(shí)器在key的過(guò)期時(shí)間來(lái)臨時(shí),對(duì)key進(jìn)行刪除
優(yōu)點(diǎn):保證內(nèi)存被盡快釋放
缺點(diǎn):
若過(guò)期key很多,刪除這些key會(huì)占用很多的CPU時(shí)間,在CPU時(shí)間緊張的情況下,CPU不能把所有的時(shí)間用來(lái)做要緊的事兒,還需要去花時(shí)間刪除這些key
定時(shí)器的創(chuàng)建耗時(shí),若為每一個(gè)設(shè)置過(guò)期時(shí)間的key創(chuàng)建一個(gè)定時(shí)器(將會(huì)有大量的定時(shí)器產(chǎn)生),性能影響嚴(yán)重
- 惰性刪除
含義:key過(guò)期的時(shí)候不刪除,每次從數(shù)據(jù)庫(kù)獲取key的時(shí)候去檢查是否過(guò)期,若過(guò)期則刪除,返回null。
優(yōu)點(diǎn):刪除操作只發(fā)生在從數(shù)據(jù)庫(kù)取出key的時(shí)候發(fā)生,而且只刪除當(dāng)前key,所以對(duì)CPU時(shí)間的占用是比較少的,而且此時(shí)的刪除是已經(jīng)到了非做不可的地步(如果此時(shí)還不刪除的話,我們就會(huì)獲取到了已經(jīng)過(guò)期的key了)
缺點(diǎn):若大量的key在超出超時(shí)時(shí)間后,很久一段時(shí)間內(nèi),都沒(méi)有被獲取過(guò),那么可能發(fā)生內(nèi)存泄露(無(wú)用的垃圾占用了大量的內(nèi)存)
- 定期刪除
含義:每隔一段時(shí)間執(zhí)行一次刪除過(guò)期key操作
優(yōu)點(diǎn):
通過(guò)限制刪除操作的時(shí)長(zhǎng)和頻率,來(lái)減少刪除操作對(duì)CPU時(shí)間的占用--處理"定時(shí)刪除"的缺點(diǎn)
定期刪除過(guò)期key--處理"惰性刪除"的缺點(diǎn)
缺點(diǎn)
在內(nèi)存友好方面,不如"定時(shí)刪除"
在CPU時(shí)間友好方面,不如"惰性刪除"
難點(diǎn)
合理設(shè)置刪除操作的執(zhí)行時(shí)長(zhǎng)(每次刪除執(zhí)行多長(zhǎng)時(shí)間)和執(zhí)行頻率(每隔多長(zhǎng)時(shí)間做一次刪除)(這個(gè)要根據(jù)服務(wù)器運(yùn)行情況來(lái)定了)
參考《Redis設(shè)計(jì)與實(shí)現(xiàn)》
緩存操作順序策略:
在上文中已經(jīng)講到了操作順序的問(wèn)題,是先操作mysql呢?還是先操作Redis呢?這個(gè)需要根據(jù)自己的業(yè)務(wù)邏輯來(lái)考量,盡量選擇影響較小且結(jié)合友好的方案來(lái)做。
代碼實(shí)現(xiàn):
這里只貼出主要的邏輯代碼,想要完整實(shí)現(xiàn)的可以到代碼倉(cāng)庫(kù)去取。
//添加
@Override
public int addArticle(Article article) {
if (articleDao.insertArticle(article) > 0) {
log.info("insert article success,save article to Redis");
RedisUtil.put(Constants.ARTICLE_CACHE_KEY + article.getId(), article);
return 1;
}
return 0;
}
//修改
@Override
public int updateArticle(Article article) {
if (article.getArticleTitle() == null || article.getArticleContent() == null || getTotalArticle(null) > 90 || article.getArticleContent().length() > 50000) {
return 0;
}
if (articleDao.updArticle(article) > 0) {
log.info("update article success,delete article in Redis and save again");
RedisUtil.del(Constants.ARTICLE_CACHE_KEY + article.getId());
RedisUtil.put(Constants.ARTICLE_CACHE_KEY + article.getId(), article);
return 1;
}
return 0;
}
//刪除
@Override
public int deleteArticle(String id) {
RedisUtil.del(Constants.ARTICLE_CACHE_KEY + id);
return articleDao.delArticle(id);
}
//查詢
@Override
public Article findById(String id) {
log.info("get article by id:" + id);
Article article = (Article) RedisUtil.get(Constants.ARTICLE_CACHE_KEY + id, Article.class);
if (article != null) {
log.info("article in Redis");
return article;
}
Article articleFromMysql = articleDao.getArticleById(id);
if (articleFromMysql != null) {
log.info("get article from mysql and save article to Redis");
RedisUtil.put(Constants.ARTICLE_CACHE_KEY + articleFromMysql.getId(), articleFromMysql);
return articleFromMysql;
}
return null;
}
結(jié)語(yǔ)
首發(fā)于我的個(gè)人博客,新的項(xiàng)目演示地址:perfect-ssm,登錄賬號(hào):admin,密碼:123456

如果有問(wèn)題或者有一些好的創(chuàng)意,歡迎給我留言,也感謝向我指出項(xiàng)目中存在問(wèn)題的朋友。
如果你想繼續(xù)了解該項(xiàng)目可以查看整個(gè)系列文章Spring+SpringMVC+MyBatis+easyUI整合系列文章,也可以到我的GitHub倉(cāng)庫(kù)或者開(kāi)源中國(guó)代碼倉(cāng)庫(kù)中查看源碼及項(xiàng)目文檔。