緩存的Cache Aside模式

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

Cache Aside

有兩個要點:git

  • 應用程序先從cache取數據,沒有獲得,則從數據庫中取數據,成功後,放到緩存中。github

  • 更新是先更新數據庫,成功後,讓緩存失效.爲何不是寫完數據庫後更新緩存?主要是怕兩個併發的寫操做致使髒數據。shell

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);
};

髒數據

一個是讀操做,可是沒有命中緩存,而後就到數據庫中取數據,此時來了一個寫操做,寫完數據庫後,讓緩存失效,而後,以前的那個讀操做再把老的數據放進去,因此,會形成髒數據。數據庫

這個case理論上會出現,不過,實際上出現的機率可能很是低,由於這個條件須要發生在讀緩存時緩存失效,並且併發着有一個寫操做。而實際上數據庫的寫操做會比讀操做慢得多,並且還要鎖表,而讀操做必需在寫操做前進入數據庫操做,而又要晚於寫操做更新緩存,全部的這些條件都具有的機率基本並不大。緩存

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>

代碼復現

這裏使用代碼復現一下這個髒數據場景。併發

  • 讀操做進來,發現沒有cache,則觸發loading,獲取數據,還沒有返回maven

  • 寫操做進來,更新數據源,invalidate緩存ide

  • loading獲取的舊數據返回,cache裏頭存的是髒數據性能

@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秒才返回,模擬引起髒緩存
                        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 以前 先觸發cache loading
        //loading那裏增長sleep,確保在invalidate以後,cache loading才返回
        //此時返回的cache就是髒數據了
        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秒才返回,模擬引起髒緩存
                    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 以前 先觸發cache loading
        //loading那裏增長sleep,確保在invalidate以後,cache loading才返回
        //此時返回的cache就是髒數據了
        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又從新觸發了一次,而後髒數據就清除了

doc

相關文章
相關標籤/搜索