緩存的Cache Aside模式

本文主要講述下緩存的Cache Aside模式。

Cache Aside

有兩個要點(diǎn):

  • 應(yīng)用程序先從cache取數(shù)據(jù)峦睡,沒有得到翎苫,則從數(shù)據(jù)庫中取數(shù)據(jù),成功后赐俗,放到緩存中。
  • 更新是先更新數(shù)據(jù)庫弊知,成功后阻逮,讓緩存失效.為什么不是寫完數(shù)據(jù)庫后更新緩存?主要是怕兩個并發(fā)的寫操作導(dǎo)致臟數(shù)據(jù)秩彤。
public V read(K key) {
  V result = cache.getIfPresent(key);
  if (result == null) {
    result = readFromDatabase(key);
    cache.put(key, result);
  }

  return result;
}

public void write(K key, V value) {
  writeToDatabase(key, value);
  cache.invalidate(key);
};

臟數(shù)據(jù)

一個是讀操作叔扼,但是沒有命中緩存,然后就到數(shù)據(jù)庫中取數(shù)據(jù)漫雷,此時來了一個寫操作瓜富,寫完數(shù)據(jù)庫后,讓緩存失效降盹,然后与柑,之前的那個讀操作再把老的數(shù)據(jù)放進(jìn)去,所以,會造成臟數(shù)據(jù)价捧。

這個case理論上會出現(xiàn)丑念,不過,實(shí)際上出現(xiàn)的概率可能非常低结蟋,因?yàn)檫@個條件需要發(fā)生在讀緩存時緩存失效脯倚,而且并發(fā)著有一個寫操作。而實(shí)際上數(shù)據(jù)庫的寫操作會比讀操作慢得多嵌屎,而且還要鎖表推正,而讀操作必需在寫操作前進(jìn)入數(shù)據(jù)庫操作,而又要晚于寫操作更新緩存宝惰,所有的這些條件都具備的概率基本并不大植榕。

maven

        <!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.5.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>22.0</version>
        </dependency>

代碼復(fù)現(xiàn)

這里使用代碼復(fù)現(xiàn)一下這個臟數(shù)據(jù)場景。

  • 讀操作進(jìn)來掌测,發(fā)現(xiàn)沒有cache内贮,則觸發(fā)loading,獲取數(shù)據(jù)汞斧,尚未返回
  • 寫操作進(jìn)來夜郁,更新數(shù)據(jù)源,invalidate緩存
  • loading獲取的舊數(shù)據(jù)返回粘勒,cache里頭存的是臟數(shù)據(jù)
@Test
    public void testCacheDirty() throws InterruptedException, ExecutionException {
        AtomicReference<Integer> db = new AtomicReference<>(1);

        LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
                .build(
                new CacheLoader<String, Integer>() {
                    public Integer load(String key) throws InterruptedException {
                        LOGGER.info("loading reading from db ...");
                        Integer v = db.get();
                        LOGGER.info("loading read from db get:{}",v);
                        Thread.sleep(1000L); //這里1秒才返回,模擬引發(fā)臟緩存
                        LOGGER.info("loading Read from db return : {}",v);
                        return v;
                    }
                }
        );

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.info("Writing to db ...");
            db.set(2);
            LOGGER.info("Wrote to db");
            cache.invalidate("k");
            LOGGER.info("Invalidated cached");
        });

        t2.start();

        //這里在t2 invalidate 之前 先觸發(fā)cache loading
        //loading那里增加sleep,確保在invalidate之后,cache loading才返回
        //此時返回的cache就是臟數(shù)據(jù)了
        LOGGER.info("fire loading cache");
        LOGGER.info("get from cache: {}",cache.get("k"));

        t2.join();

        for(int i=0;i<3;i++){
            LOGGER.info("get from cache: {}",cache.get("k"));
        }
    }

輸出

15:54:05.751 [main] INFO com.example.demo.CacheTest - fire loading cache
15:54:05.772 [main] INFO com.example.demo.CacheTest - loading reading from db ...
15:54:05.772 [main] INFO com.example.demo.CacheTest - loading read from db get:1
15:54:06.253 [Thread-1] INFO com.example.demo.CacheTest - Writing to db ...
15:54:06.253 [Thread-1] INFO com.example.demo.CacheTest - Wrote to db
15:54:06.253 [Thread-1] INFO com.example.demo.CacheTest - Invalidated cached
15:54:06.778 [main] INFO com.example.demo.CacheTest - loading Read from db return : 1
15:54:06.782 [main] INFO com.example.demo.CacheTest - get from cache: 1
15:54:06.782 [main] INFO com.example.demo.CacheTest - get from cache: 1
15:54:06.782 [main] INFO com.example.demo.CacheTest - get from cache: 1
15:54:06.782 [main] INFO com.example.demo.CacheTest - get from cache: 1

使用caffeine

@Test
    public void testCacheDirty() throws InterruptedException, ExecutionException {
        AtomicReference<Integer> db = new AtomicReference<>(1);

        com.github.benmanes.caffeine.cache.LoadingCache<String, Integer> cache = Caffeine.newBuilder()
                .build(key -> {
                    LOGGER.info("loading reading from db ...");
                    Integer v = db.get();
                    LOGGER.info("loading read from db get:{}",v);
                    Thread.sleep(1000L); //這里1秒才返回,模擬引發(fā)臟緩存
                    LOGGER.info("loading Read from db return : {}",v);
                    return v;
                });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.info("Writing to db ...");
            db.set(2);
            LOGGER.info("Wrote to db");
            cache.invalidate("k");
            LOGGER.info("Invalidated cached");
        });

        t2.start();

        //這里在t2 invalidate 之前 先觸發(fā)cache loading
        //loading那里增加sleep,確保在invalidate之后,cache loading才返回
        //此時返回的cache就是臟數(shù)據(jù)了
        LOGGER.info("fire loading cache");
        LOGGER.info("get from cache: {}",cache.get("k"));

        t2.join();

        for(int i=0;i<3;i++){
            LOGGER.info("get from cache: {}",cache.get("k"));
        }
    }

輸出

16:05:10.141 [main] INFO com.example.demo.CacheTest - fire loading cache
16:05:10.153 [main] INFO com.example.demo.CacheTest - loading reading from db ...
16:05:10.153 [main] INFO com.example.demo.CacheTest - loading read from db get:1
16:05:10.634 [Thread-1] INFO com.example.demo.CacheTest - Writing to db ...
16:05:10.635 [Thread-1] INFO com.example.demo.CacheTest - Wrote to db
16:05:11.172 [main] INFO com.example.demo.CacheTest - loading Read from db return : 1
16:05:11.172 [main] INFO com.example.demo.CacheTest - get from cache: 1
16:05:11.172 [Thread-1] INFO com.example.demo.CacheTest - Invalidated cached
16:05:11.172 [main] INFO com.example.demo.CacheTest - loading reading from db ...
16:05:11.172 [main] INFO com.example.demo.CacheTest - loading read from db get:2
16:05:12.177 [main] INFO com.example.demo.CacheTest - loading Read from db return : 2
16:05:12.177 [main] INFO com.example.demo.CacheTest - get from cache: 2
16:05:12.177 [main] INFO com.example.demo.CacheTest - get from cache: 2
16:05:12.177 [main] INFO com.example.demo.CacheTest - get from cache: 2

這里可以看到invalidate的時候竞端,loading又重新觸發(fā)了一次,然后臟數(shù)據(jù)就清除了

doc

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庙睡,一起剝皮案震驚了整個濱河市事富,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乘陪,老刑警劉巖统台,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啡邑,居然都是意外死亡贱勃,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門谤逼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贵扰,“玉大人,你說我怎么就攤上這事流部∑萑疲” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵枝冀,是天一觀的道長舞丛。 經(jīng)常有香客問我耘子,道長,這世上最難降的妖魔是什么瓷马? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任拴还,我火速辦了婚禮,結(jié)果婚禮上欧聘,老公的妹妹穿的比我還像新娘片林。我一直安慰自己,他們只是感情好怀骤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布费封。 她就那樣靜靜地躺著,像睡著了一般蒋伦。 火紅的嫁衣襯著肌膚如雪弓摘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天痕届,我揣著相機(jī)與錄音韧献,去河邊找鬼。 笑死研叫,一個胖子當(dāng)著我的面吹牛锤窑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嚷炉,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼渊啰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了申屹?” 一聲冷哼從身側(cè)響起绘证,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哗讥,沒想到半個月后嚷那,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杆煞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年魏宽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片索绪。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡砚婆,死狀恐怖耗拓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情和屎,我是刑警寧澤窄坦,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布唤反,位于F島的核電站凳寺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏彤侍。R本人自食惡果不足惜肠缨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盏阶。 院中可真熱鬧晒奕,春花似錦、人聲如沸名斟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砰盐。三九已至闷袒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岩梳,已是汗流浹背囊骤。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冀值,地道東北人也物。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像池摧,于是被迫代替她去往敵國和親焦除。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 理論總結(jié) 它要解決什么樣的問題作彤? 數(shù)據(jù)的訪問膘魄、存取、計算太慢竭讳、太不穩(wěn)定创葡、太消耗資源,同時绢慢,這樣的操作存在重復(fù)性灿渴。因...
    jiangmo閱讀 2,851評論 0 11
  • 前言 主題是Mybatis一級和二級緩存的應(yīng)用及源碼分析。希望在本場chat結(jié)束后胰舆,能夠幫助讀者朋友明白以下三點(diǎn)骚露。...
    余平的余_余平的平閱讀 1,327評論 0 12
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,140評論 25 707
  • 本文已授權(quán)Gitchat獨(dú)家發(fā)布倦零,未經(jīng)Gitchat許可误续,不得轉(zhuǎn)載吨悍。 我,后端Java工程師蹋嵌,現(xiàn)在美團(tuán)點(diǎn)評工作育瓜。愛...
    凱倫說閱讀 42,720評論 25 146
  • 1.JDK:調(diào)試、編譯源文件栽烂,生成文檔等躏仇,包含各種開發(fā)工具 2.JRE:運(yùn)行字節(jié)碼文件,包含基礎(chǔ)API和JVM 3...
    吳迪好閱讀 237評論 0 1