欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis作為緩存應(yīng)用的情形詳細(xì)分析

 更新時(shí)間:2023年01月18日 15:17:21   作者:氵奄不死的魚  
實(shí)際開發(fā)中緩存處理是必須的,不可能我們每次客戶端去請求一次服務(wù)器,服務(wù)器每次都要去數(shù)據(jù)庫中進(jìn)行查找,為什么要使用緩存?說到底是為了提高系統(tǒng)的運(yùn)行速度

為什么使用緩存

Redis是一個(gè)內(nèi)存型數(shù)據(jù)庫,也就是說,所有的數(shù)據(jù)都會存在與內(nèi)存中,基于Redis的高性能特性,我們將Redis用在緩存場景非常廣泛。使用起來方便,響應(yīng)也是遠(yuǎn)超關(guān)系型數(shù)據(jù)庫。

應(yīng)用場景

Redis的應(yīng)用場景非常廣泛。雖然Redis是一個(gè)key-value的內(nèi)存數(shù)據(jù)庫,但在實(shí)際場景中,Redis經(jīng)常被作為緩存來使用,如面對數(shù)據(jù)高并發(fā)的讀寫、海量數(shù)據(jù)的讀寫等。

舉個(gè)例子,A網(wǎng)站首頁一天有100萬人訪問,其中有一個(gè)“積分商城”的板塊,要直接從數(shù)據(jù)庫查詢,那么一天就要多消耗100萬次數(shù)據(jù)庫請求。如果將這些數(shù)據(jù)儲存到Redis(內(nèi)存)中,要用的時(shí)候,直接從內(nèi)存調(diào)取,不僅可以大大節(jié)省系統(tǒng)直接讀取磁盤來獲得數(shù)據(jù)的IO開銷,提高服務(wù)器的資源利用率,還能極大地提升速度。

比如很多大型電商網(wǎng)站、視頻網(wǎng)站和游戲應(yīng)用等,存在大規(guī)模數(shù)據(jù)訪問,對數(shù)據(jù)查詢效率要求高。Redis服務(wù)可實(shí)現(xiàn)頁面緩存、應(yīng)用緩存、狀態(tài)緩存、事件并行處理,能夠有效減少數(shù)據(jù)庫磁盤IO,提高數(shù)據(jù)查詢效率,減輕管理維護(hù)工作量,降低數(shù)據(jù)庫存儲成本。對傳統(tǒng)磁盤數(shù)據(jù)庫是一個(gè)重要的補(bǔ)充,成為了互聯(lián)網(wǎng)應(yīng)用,尤其是支持高并發(fā)訪問的互聯(lián)網(wǎng)應(yīng)用必不可少的基礎(chǔ)服務(wù)之一。

具體而言,分布式緩存Redis可用于以下場景:

1、頁面緩存

Redis可將Web頁面的內(nèi)容片段,包括HTML,CSS和圖片等靜態(tài)數(shù)據(jù),緩存到Redis實(shí)例,提高網(wǎng)站的訪問性能。

比如在電商類應(yīng)用中,熱銷商品展示、秒殺推薦等數(shù)據(jù)面臨高并發(fā)讀的壓力,分布式緩存Redis的高并發(fā)及靈活擴(kuò)展,可輕松支持此類應(yīng)用。

2、狀態(tài)緩存

Redis可將Session會話狀態(tài)及應(yīng)用橫向擴(kuò)展時(shí)的狀態(tài)數(shù)據(jù)等緩存到DCS實(shí)例,實(shí)現(xiàn)狀態(tài)數(shù)據(jù)共享。在應(yīng)對游戲應(yīng)用中爆發(fā)式增長的玩家數(shù)據(jù)存儲和讀寫請求時(shí),使用分布式緩存Redis可通過將熱點(diǎn)數(shù)據(jù)放入緩存,加快用戶端訪問速度,提升用戶體驗(yàn)。

3、應(yīng)用對象緩存

Redis可作為服務(wù)層的二級緩存對外提供服務(wù),減輕數(shù)據(jù)庫的負(fù)載壓力,加速應(yīng)用訪問。

4、事件緩存

Redis可提供針對事件流的連續(xù)查詢(continuous query)處理技術(shù),滿足實(shí)時(shí)性需求。

使用緩存的收益和成本

如圖左側(cè)為客戶端直接調(diào)用存儲層的架構(gòu),右側(cè)為比較典型的緩存層+存儲層架構(gòu),下面分析一下緩存加入后帶來的收益和成本。

收益:

l 加速讀寫:因?yàn)榫彺嫱ǔ6际侨珒?nèi)存的,而存儲層通常讀寫性能不夠強(qiáng)悍(例如MySQL),通過緩存的使用可以有效地加速讀寫,優(yōu)化用戶體驗(yàn)。

l 降低后端負(fù)載:幫助后端減少訪問量和復(fù)雜計(jì)算(例如很復(fù)雜的SQL語句),在很大程度降低了后端的負(fù)載。

成本:

l 數(shù)據(jù)不一致性:緩存層和存儲層的數(shù)據(jù)存在著一定時(shí)間窗口的不一致性,時(shí)間窗口跟更新策略有關(guān)。

l 代碼維護(hù)成本:加入緩存后,需要同時(shí)處理緩存層和存儲層的邏輯,增大了開發(fā)者維護(hù)代碼的成本。

l 運(yùn)維成本:以Redis Cluster為例,加入后無形中增加了運(yùn)維成本。

緩存不一致

一致性

1、強(qiáng)一致性

如果你的項(xiàng)目對緩存的要求是強(qiáng)一致性的,那么請不要使用緩存。這種一致性級別是最符合用戶直覺的,它要求系統(tǒng)寫入什么,讀出來的也會是什么,用戶體驗(yàn)好,但實(shí)現(xiàn)起來往往對系統(tǒng)的性能影響大。

2、弱一致性

這種一致性級別約束了系統(tǒng)在寫入成功后,不承諾立即可以讀到寫入的值,也不承諾多久之后數(shù)據(jù)能夠達(dá)到一致,但會盡可能地保證到某個(gè)時(shí)間級別(比如秒級別)后,數(shù)據(jù)能夠達(dá)到一致狀態(tài)

3**、最終一致性**

最終一致性是弱一致性的一個(gè)特例,系統(tǒng)會保證在一定時(shí)間內(nèi),能夠達(dá)到一個(gè)數(shù)據(jù)一致的狀態(tài)。這里之所以將最終一致性單獨(dú)提出來,是因?yàn)樗侨跻恢滦灾蟹浅M瞥绲囊环N一致性模型,也是業(yè)界在大型分布式系統(tǒng)的數(shù)據(jù)一致性上比較推崇的模型。一般情況下,高可用只確保最終一致性,不確保強(qiáng)一致性。

強(qiáng)一致性,讀請求和寫請求會串行化,串到一個(gè)內(nèi)存隊(duì)列里去,這樣會大大增加系統(tǒng)的處理效率,吞吐量也會大大降低。

業(yè)務(wù)場景

在絕大多數(shù)的系統(tǒng)中數(shù)據(jù)庫往往是用戶并發(fā)訪問最薄弱的地方,并且在高并發(fā)下的讀多寫少的情況下,我們往往會借助一些中間鍵,來解決數(shù)據(jù)訪問過大時(shí)造成的數(shù)據(jù)庫宕機(jī)情況,例如我們可以使用Redis來作為緩存,讓請求先訪問到Redis,而不是直接訪問數(shù)據(jù)庫。而在這種業(yè)務(wù)場景下,可能會出現(xiàn)緩存和數(shù)據(jù)庫數(shù)據(jù)不一致性的問題。

問題產(chǎn)生的原因

一般來說讀取緩存步驟是不會有什么問題的,但是一旦涉及到數(shù)據(jù)更新,也就是數(shù)據(jù)庫和緩存都操作,就容易出現(xiàn)緩存(Redis)和數(shù)據(jù)庫(MySQL)間的數(shù)據(jù)一致性問題。

在數(shù)據(jù)更新時(shí),我們需要做以下兩步:

  • 操作MySQL
  • 操作緩存

但是無論是先執(zhí)行步驟1還是先執(zhí)行步驟2,都有可能出現(xiàn)數(shù)據(jù)不一致的情況,主要是因?yàn)樽x寫是并發(fā)的,我們無法保證他們的先后順序。

相關(guān)策略

先做一個(gè)說明,從理論上來說,給緩存設(shè)置過期時(shí)間,是保證最終一致性的解決方案(如果要求強(qiáng)一致性的話,我認(rèn)為沒有必要添加緩存了,直接走數(shù)據(jù)庫)。這種前提下,我們可以對存入緩存的數(shù)據(jù)設(shè)置過期時(shí)間,所有的寫操作以數(shù)據(jù)庫為準(zhǔn),對緩存操作只是盡最大努力即可。也就是說如果數(shù)據(jù)庫寫成功,緩存更新失敗,那么只要到達(dá)過期時(shí)間,則后面的讀請求自然會從數(shù)據(jù)庫中讀取新值然后回填緩存。因此,接下來討論的思路不依賴于給緩存設(shè)置過期時(shí)間這個(gè)方案。

給出了三種更新策略:

  • 先更新數(shù)據(jù)庫,再更新緩存
  • 先刪除緩存,再更新數(shù)據(jù)庫
  • 先更新數(shù)據(jù)庫,在刪除緩存

先更新數(shù)據(jù)庫值再更新緩存值

最不可能選擇的策略,原因是此種策略可能會在線程安全的角度和業(yè)務(wù)場景角度生成臟數(shù)據(jù)和性能問題。

原因一:線程安全的角度

同時(shí)有請求A和請求B進(jìn)行更新操作,那么就會出現(xiàn)

  • 請求A更新數(shù)據(jù)庫
  • 請求B更新數(shù)據(jù)庫
  • 請求B更新緩存
  • 請求A更新緩存

這就出現(xiàn)請求A更新緩存應(yīng)該比請求B更新緩存早才對,但是因?yàn)榫W(wǎng)絡(luò)等原因,B比A更早更新了緩存。這就導(dǎo)致了臟數(shù)據(jù),因此不考慮。

業(yè)務(wù)場景角度

(1)如果是寫數(shù)據(jù)庫場景比較多,而讀數(shù)據(jù)場景比較少的業(yè)務(wù)需求,那么采用這種方案就會導(dǎo)致,數(shù)據(jù)壓根還沒讀到,緩存就被頻繁的更新,浪費(fèi)性能,緩存此類數(shù)據(jù),沒有很大的意義。

(2)如果是寫入數(shù)據(jù)庫的值,并不是直接寫入緩存的,而是要經(jīng)過一系列復(fù)雜的計(jì)算再寫入緩存。那么,每次寫入數(shù)據(jù)庫后,都再次計(jì)算寫入緩存的值,無疑是浪費(fèi)性能的。顯然,刪除緩存更為適合。

后面兩種策略都是對緩存進(jìn)行刪除,這里先做一個(gè)解釋。

例子:數(shù)據(jù)庫在1小時(shí)內(nèi)更新1000次那么緩存也更新1000次,但是這個(gè)緩存可能在1小時(shí)內(nèi)只被讀了1次,那么就沒有必要更新1000次了。反過來,如果是刪除的話,那么也只是做了1次刪除操作,當(dāng)緩存真正被讀取的時(shí)候才去更新。

刪除緩存值再更新數(shù)據(jù)庫值

  • 請求A進(jìn)行更新操作,首先刪除緩存
  • 請求B查詢發(fā)現(xiàn)緩存不存在
  • 請求B去數(shù)據(jù)庫查詢得到舊值
  • 請求B將舊值寫入緩存
  • 請求A將新值寫入數(shù)據(jù)庫

上述情況就會導(dǎo)致不一致的情形出現(xiàn)。而且,如果不采用給緩存設(shè)置過期時(shí)間策略,該數(shù)據(jù)永遠(yuǎn)都是臟數(shù)據(jù)。我們可以采用延遲雙刪策略,來解決這個(gè)問題。

相對應(yīng)的步驟:

  • 先淘汰緩存
  • 再寫數(shù)據(jù)庫
  • 休眠t秒,再次淘汰緩存

這么做的目的,就是確保讀請求結(jié)束,寫請求可以刪除讀請求造成的緩存臟數(shù)據(jù)。

// 偽代碼
public void updateDb(String key,Object data) {
    redis.delKey(key);
    db.updateData(data);    
    Thread.sleep(t);
    redis.delKey(key);
}

如果系統(tǒng)中MySQL使用了讀寫分離模式,那么有可能會出現(xiàn)在主從同步?jīng)]有完成時(shí),讀請求就去讀取數(shù)據(jù)了,這時(shí)候就會讀取到舊值,這里我們可以延長睡眠時(shí)間,讓主從同步完成后在進(jìn)行一次刪除(如果不考慮主從的情況下,采用雙刪不用加延時(shí)時(shí)間也是可以保證一直性的)。

先更新數(shù)據(jù)庫值在刪除緩存值

假設(shè)有兩個(gè)請求,請求A進(jìn)行更新操作,請求B進(jìn)行查詢操作。

那么會出現(xiàn)如下情形:

  • 請求A進(jìn)行更新操作,首先更新數(shù)據(jù)庫
  • 請求B進(jìn)行查詢操作,擊中緩存,得到舊值
  • 請求A進(jìn)行刪除緩存操作

在這種情況下如果其他線程并發(fā)讀緩存的請求不多,那么,就不會有很多請求讀取到舊值。而且,請求 A 一般也會很快刪除緩存值,這樣一來,其他線程再次讀取時(shí),就會發(fā)生緩存缺失,進(jìn)而從數(shù)據(jù)庫中讀取最新值。所以,這種情況對業(yè)務(wù)的影響較小。

無論是策略2還是策略3都有可能會出現(xiàn)這種情況:刪除緩存失敗,這時(shí)我們可以采用重試機(jī)制來保證數(shù)據(jù)的一致性。

方案的詳細(xì)設(shè)計(jì)

在相關(guān)策略的調(diào)用中,雖然提出了一些簡單解決方案,但是沒有考慮到列如 緩存刪除失敗,數(shù)據(jù)庫更新失敗等情況,因此需要增加重試策略,但是還是可能會出現(xiàn)比較不一致的問題,此處詳細(xì)介紹幾種方案。

流程如下:

  • 更新數(shù)據(jù)庫數(shù)據(jù);
  • 緩存因?yàn)榉N種問題刪除失敗
  • 將需要?jiǎng)h除的key發(fā)送至消息隊(duì)列
  • 自己消費(fèi)消息,獲得需要?jiǎng)h除的key
  • 繼續(xù)重試刪除操作,直到成功

如果能夠成功地刪除或更新,我們就要把這些值從消息隊(duì)列中去除,以免重復(fù)操作,此時(shí),我們也可以保證數(shù)據(jù)庫和緩存的數(shù)據(jù)一致了。否則的話,我們還需要再次進(jìn)行重試。如果重試超過的一定次數(shù),還是沒有成功,我們就需要向業(yè)務(wù)層發(fā)送報(bào)錯(cuò)信息了。

// 偽代碼
public void updateDb(String key,Object data){
    db.updateData(data);
    if (!redis.delKey(key)) {
        mq.send(key);
        new Thread(() -> asyncDel()).start();
    }    
}
// 異步重試
private void asyncDel() {
    int count = 0;
    String key = mq.get();
    while(!redis.delKey(key)) {
        count++;
        if (count > 5) {
            throw new DelFailException();        
        }
    }
    mq.remove(key);
}

這種雖然可以解決,但是會對業(yè)務(wù)代碼造成侵入,而且還需要去維護(hù)消息隊(duì)列,如果可以容忍的話,我覺得是可選的方案之一。

注意 需要使用有序的消息隊(duì)列,保證消息的有序性。重試刪除

訂閱binlog

業(yè)務(wù)代碼只會操作數(shù)據(jù)庫,不操作緩存。同時(shí)啟動一個(gè)訂閱binlog的程序去監(jiān)聽刪除操作,然后投遞到消息隊(duì)列中。再啟動一個(gè)消費(fèi)者,根據(jù)消息去刪除緩存。

canal是用來模擬MySQL slave,來訂閱MySQL master 的binlog。

異步重試

總結(jié)

對于讀多寫少的數(shù)據(jù),請使用緩存。

為了保持?jǐn)?shù)據(jù)庫和緩存的一致性,會導(dǎo)致系統(tǒng)吞吐量的下降。

為了保持?jǐn)?shù)據(jù)庫和緩存的一致性,會導(dǎo)致業(yè)務(wù)代碼邏輯復(fù)雜。

緩存做不到絕對一致性,但可以做到最終一致性。

對于需要保證緩存數(shù)據(jù)庫數(shù)據(jù)一致的情況,請盡量考慮對一致性到底有多高要求,選定合適的方案,避免過度設(shè)計(jì)。

緩存問題

緩存穿透

問題描述

緩存穿透是指查詢一個(gè)根本不存在的數(shù)據(jù),緩存層和存儲層都不會命中,通常出于容錯(cuò)的考慮,如果從存儲層查不到數(shù)據(jù)則不寫入緩存層,如下圖所示

整個(gè)過程分為如下3步:

緩存層不命中。存儲層不命中,不將空結(jié)果寫回緩存。返回空結(jié)果。

緩存穿透將導(dǎo)致不存在的數(shù)據(jù)每次請求都要到存儲層去查詢,失去了緩存保護(hù)后端存儲的意義。

緩存穿透問題可能會使后端存儲負(fù)載加大,由于很多后端存儲不具備高并發(fā)性,甚至可能造成后端存儲宕掉。通常可以在程序中分別統(tǒng)計(jì)總調(diào)用數(shù)、緩存層命中數(shù)、存儲層命中數(shù),如果發(fā)現(xiàn)大量存儲層空命中,可能就是出現(xiàn)了緩存穿透問題。

解決方案

造成緩存穿透的基本原因有兩個(gè)。第一,自身業(yè)務(wù)代碼或者數(shù)據(jù)出現(xiàn)問題,第二,一些惡意攻擊、爬蟲等造成大量空命中。下面我們來看一下如何解決緩存穿透問題。

緩存空對象

如圖所示,當(dāng)?shù)?步存儲層不命中后,仍然將空對象保留到緩存層中,之后再訪問這個(gè)數(shù)據(jù)將會從緩存中獲取,這樣就保護(hù)了后端數(shù)據(jù)源。

緩存空對象會有兩個(gè)問題:第一,空值做了緩存,意味著緩存層中存了更多的鍵,需要更多的內(nèi)存空間,比較有效的方法是針對這類數(shù)據(jù)設(shè)置一個(gè)較短的過期時(shí)間,讓其自動剔除。第二,緩存層和存儲層的數(shù)據(jù)會有一段時(shí)間窗口的不一致,可能會對業(yè)務(wù)有一定影響。例如過期時(shí)間設(shè)置為5分鐘,如果此時(shí)存儲層添加了這個(gè)數(shù)據(jù),那此段時(shí)間就會出現(xiàn)緩存層和存儲層數(shù)據(jù)的不一致,此時(shí)可以利用消息系統(tǒng)或者其他方式清除掉緩存層中的空對象。

布隆過濾器攔截

布隆過濾器:實(shí)際上是一個(gè)很長的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都比一般的算法要好的多,缺點(diǎn)是有一定的誤識別率和刪除困難??梢愿嬖V你某樣?xùn)|西一定不存在或者可能存在。

如圖所示,在訪問緩存層和存儲層之前,將存在的key用布隆過濾器提前保存起來,做第一層攔截。例如:一個(gè)推薦系統(tǒng)有4億個(gè)用戶id,每個(gè)小時(shí)算法工程師會根據(jù)每個(gè)用戶之前歷史行為計(jì)算出推薦數(shù)據(jù)放到存儲層中,但是最新的用戶由于沒有歷史行為,就會發(fā)生緩存穿透的行為,為此可以將所有推薦數(shù)據(jù)的用戶做成布隆過濾器。如果布隆過濾器認(rèn)為該用戶id不存在,那么就不會訪問存儲層,在一定程度保護(hù)了存儲層。

兩種方案比對

緩存雪崩

如圖描述了什么是緩存雪崩:由于緩存層承載著大量請求,有效地保護(hù)了存儲層,但是如果緩存層由于某些原因不能提供服務(wù),于是所有的請求都會達(dá)到存儲層,存儲層的調(diào)用量會暴增,造成存儲層也會級聯(lián)宕機(jī)的情況。

預(yù)防和解決緩存雪崩問題,可以從以下三個(gè)方面進(jìn)行著手。

(1) 保證緩存層服務(wù)高可用性。如果緩存層設(shè)計(jì)成高可用的,即使個(gè)別節(jié)點(diǎn)、個(gè)別機(jī)器、甚至是機(jī)房宕掉,依然可以提供服務(wù),例如前面介紹過的Redis Sentinel和Redis Cluster都實(shí)現(xiàn)了高可用。

(2) 依賴隔離組件為后端限流并降級。無論是緩存層還是存儲層都會有出錯(cuò)的概率,可以將它們視同為資源。作為并發(fā)量較大的系統(tǒng),假如有一個(gè)資源不可用,可能會造成線程全部阻塞在這個(gè)資源上,造成整個(gè)系統(tǒng)不可用。降級機(jī)制在高并發(fā)系統(tǒng)中是非常普遍的。實(shí)際項(xiàng)目中,我們需要對重要的資源(例如Redis、MySQL、HBase、外部接口)都進(jìn)行隔離,讓每種資源都單獨(dú)運(yùn)行在自己的線程池中,即使個(gè)別資源出現(xiàn)了問題,對其他服務(wù)沒有影響。但是線程池如何管理,比如如何關(guān)閉資源池、開啟資源池、資源池閥值管理,這些做起來還是相當(dāng)復(fù)雜的。這里推薦使用Java依賴隔離工具Hystrix,他是解決依賴隔離的利器。

(3) 提前演練。在項(xiàng)目上線前,演練緩存層宕掉后,應(yīng)用以及后端的負(fù)載情況以及可能出現(xiàn)的問題,在此基礎(chǔ)上做一些預(yù)案設(shè)定。

緩存擊穿(熱點(diǎn)數(shù)據(jù)集中失效)

問題描述

當(dāng)一個(gè)key是熱點(diǎn)key,并發(fā)量很大,而且重建緩存不能在短時(shí)間完成,在緩存失效的一瞬間,就會有大量的線程來重建緩存,造成后端負(fù)載加大,甚至讓應(yīng)用崩潰,這就叫緩存擊穿。如下圖:

解決方案

互斥鎖

此方法只允許一個(gè)線程重建緩存,其他線程等待重建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)即可,整個(gè)過程如圖所示。

永遠(yuǎn)不過期

“永遠(yuǎn)不過期”包含兩層意思:

l 從緩存層面來看,確實(shí)沒有設(shè)置過期時(shí)間,所以不會出現(xiàn)熱點(diǎn)key過期后產(chǎn)生的問題,也就是“物理”不過期。

l 從功能層面來看,為每個(gè)value設(shè)置一個(gè)邏輯過期時(shí)間,當(dāng)發(fā)現(xiàn)超過邏輯過期時(shí)間后,會使用單獨(dú)的線程去構(gòu)建緩存。

整個(gè)過程如圖所示:

此方法有效杜絕了熱點(diǎn)key產(chǎn)生的問題,但唯一不足的就是重構(gòu)緩存期間,會出現(xiàn)數(shù)據(jù)不一致的情況,這取決于應(yīng)用方是否容忍這種不一致。

兩種方案對比

到此這篇關(guān)于Redis作為緩存應(yīng)用的情形詳細(xì)分析的文章就介紹到這了,更多相關(guān)Redis作為緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論