本文主要講述下緩存的Cache Aside模式。html
有兩個要點: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理論上會出現,不過,實際上出現的機率可能很是低,由於這個條件須要發生在讀緩存時緩存失效,並且併發着有一個寫操做。而實際上數據庫的寫操做會比讀操做慢得多,並且還要鎖表,而讀操做必需在寫操做前進入數據庫操做,而又要晚於寫操做更新緩存,全部的這些條件都具有的機率基本並不大。緩存
<!-- 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
@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又從新觸發了一次,而後髒數據就清除了