序
本文主要講述下緩存的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ù)就清除了