Redis源碼及實(shí)戰(zhàn)分析(二) 如何在生產(chǎn)環(huán)境使用緩存

這讓我想起了校招回廈門的第二站,當(dāng)時(shí)氣血上頭應(yīng)聘5年經(jīng)驗(yàn)的分布式網(wǎng)絡(luò)工程師,由于實(shí)習(xí)期間負(fù)責(zé)支付模塊中用到redis的緩存一致性,所以展開了一場(chǎng)持續(xù)1小時(shí)的緩存之爭(zhēng)

### 項(xiàng)目中緩存是如何使用的?

根據(jù)緩存一致性,在查詢支付數(shù)據(jù)的時(shí)候,為了及時(shí)反饋訂單,就使用redis保存訂單支付和用戶在線狀態(tài)

### 為什么要用緩存?

用緩存,主要有兩個(gè)用途:**高性能**、**高并發(fā)**。

#### 高性能

假設(shè)這么個(gè)場(chǎng)景,你有個(gè)操作,一個(gè)請(qǐng)求過來耗時(shí) 600ms查數(shù)據(jù)庫,但是這個(gè)結(jié)果不會(huì)經(jīng)常變動(dòng),或者變了也可以不用立即反饋給用戶。應(yīng)該怎么做?

用緩存,每次先寫數(shù)據(jù)庫,再寫緩存。每次先查緩存,再查數(shù)據(jù)庫。

#### 高并發(fā)


但這樣有個(gè)問題,緩存如果某一時(shí)刻為null,那豈不是所有請(qǐng)求都打到數(shù)據(jù)庫?那如果數(shù)據(jù)庫為null,那豈不是所有請(qǐng)求都打到緩存?

enmm緩存是走內(nèi)存的一部分.....好吧我想不出來

其實(shí)要根據(jù)具體需求跟業(yè)務(wù)方協(xié)商能不能加機(jī)器

但實(shí)際上機(jī)器也是要維護(hù)成本的,所以我在想,如果只有2-3臺(tái)機(jī)器的話,那我們?nèi)绾螒?yīng)對(duì)呢?

常見的緩存場(chǎng)景有以下幾個(gè)

雙寫不一致

就像自己之前提到的

  • 讀的時(shí)候,先讀緩存,緩存沒有的話,就讀數(shù)據(jù)庫,然后取出數(shù)據(jù)后放入緩存,同時(shí)返回響應(yīng)。
  • 更新的時(shí)候,先更新數(shù)據(jù)庫,然后再刪除緩存。

缺點(diǎn)在于:
一個(gè)請(qǐng)求過來,去讀緩存,發(fā)現(xiàn)緩存空了,去查詢數(shù)據(jù)庫,查到了修改前的舊數(shù)據(jù),放到了緩存中。隨后數(shù)據(jù)變更的程序完成了數(shù)據(jù)庫的修改。

方案1:我自己想的是一般是先放入隊(duì)列,然后起一個(gè)短任務(wù)輪詢最新的請(qǐng)求,如果是寫超時(shí)沒關(guān)系,畢竟需要等待最新的寫請(qǐng)求,到時(shí)間后自動(dòng)更新。但是讀請(qǐng)求超時(shí),就會(huì)發(fā)生緩存與數(shù)據(jù)庫不一致,所以我的方案是做讀寫分離,并行化請(qǐng)求,需要頻繁訪問的接口先讀主庫,其他接口走從庫。

方案2:一位前輩的建議是更新數(shù)據(jù)的時(shí)候,根據(jù)數(shù)據(jù)的唯一標(biāo)識(shí),將操作路由之后,發(fā)送到一個(gè) jvm 內(nèi)部隊(duì)列中。讀取數(shù)據(jù)的時(shí)候,如果發(fā)現(xiàn)數(shù)據(jù)不在緩存中,那么將重新讀取數(shù)據(jù)+更新緩存的操作,根據(jù)唯一標(biāo)識(shí)路由之后,也發(fā)送同一個(gè) jvm 內(nèi)部隊(duì)列中。

緩存雪崩、緩存穿透

  • 緩存雪崩
    對(duì)于系統(tǒng) A,假設(shè)每天高峰期每秒 5000 個(gè)請(qǐng)求,本來緩存在高峰期可以扛住每秒 4000 個(gè)請(qǐng)求,但是緩存機(jī)器意外發(fā)生了全盤宕機(jī)。緩存掛了,此時(shí) 1 秒 5000 個(gè)請(qǐng)求全部落數(shù)據(jù)庫,數(shù)據(jù)庫報(bào)警完就掛了。此時(shí),如果重啟數(shù)據(jù)庫,數(shù)據(jù)庫立馬又被新的流量給打死了。

方案1:使用熔斷器Hystrix
對(duì)重要的資源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都進(jìn)行隔離,讓每種資源都單獨(dú)運(yùn)行在自己的線程池中,即使個(gè)別資源出現(xiàn)了問題,對(duì)其他服務(wù)沒有影響。

方案2:使用redis持久化機(jī)制
一旦重啟,自動(dòng)從磁盤上加載數(shù)據(jù),快速恢復(fù)緩存數(shù)據(jù)。

  • 緩存穿透
    當(dāng)前 key 是一個(gè)熱點(diǎn) key( 例如一個(gè)熱門的娛樂新聞),并發(fā)量非常大。
    重建緩存不能在短時(shí)間完成,可能是一個(gè)復(fù)雜計(jì)算,例如復(fù)雜的 SQL、多次 IO、多個(gè)依賴等。


    33.png

方案1:避免key過期,可以將熱點(diǎn)數(shù)據(jù)設(shè)置為永遠(yuǎn)不過期。但如果重建緩存不能再短時(shí)間完成,又會(huì)出現(xiàn)數(shù)據(jù)不一致的情況

這里需要避免下面三個(gè)問題

  • 減少重建緩存的次數(shù)
  • 數(shù)據(jù)盡可能一致
  • 較少的潛在危險(xiǎn)

方案2:基于 redis or zookeeper 實(shí)現(xiàn)互斥鎖,等待第一個(gè)請(qǐng)求構(gòu)建完緩存之后,再釋放鎖,進(jìn)而其它請(qǐng)求才能通過該 key 訪問數(shù)據(jù)。

緩存并發(fā)競(jìng)爭(zhēng)

多客戶端同時(shí)并發(fā)寫一個(gè) key,可能本來應(yīng)該先到的數(shù)據(jù)后到了,導(dǎo)致數(shù)據(jù)版本錯(cuò)了;或者是多客戶端同時(shí)獲取一個(gè) key,修改值之后再寫回去,只要順序錯(cuò)了,數(shù)據(jù)就錯(cuò)了。

方案1:基于時(shí)間戳的樂觀鎖
你要寫入緩存的數(shù)據(jù),都是從 mysql 里查出來的,都得寫入 mysql 中,寫入 mysql 中的時(shí)候必須保存一個(gè)時(shí)間戳,從 mysql 查出來的時(shí)候,時(shí)間戳也查出來。

每次要寫之前,先判斷一下當(dāng)前這個(gè) value 的時(shí)間戳是否比緩存里的 value 的時(shí)間戳要新。如果是的話,那么可以寫,否則,就不能用舊的數(shù)據(jù)覆蓋新的數(shù)據(jù)。但缺點(diǎn)在于假設(shè)客戶A,B,C并發(fā)寫key,假設(shè)有一個(gè)審核場(chǎng)景,必須限制A,B,C順序?qū)?。那么就?huì)出現(xiàn)亂序的情況

方案2:基于redis的setnx的悲觀鎖

當(dāng)要寫入key的時(shí)候,給每個(gè)要訪問的定時(shí)任務(wù)一定的邏輯時(shí)間間隔,先進(jìn)入一個(gè)內(nèi)存隊(duì)列等待鎖,然后排隊(duì)寫入Key

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容