快取的Cache Aside模式
序
本文主要講述下快取的Cache Aside模式。
Cache Aside
有兩個要點:
- 應用程式先從cache取資料,沒有得到,則從資料庫中取資料,成功後,放到快取中。
- 更新是先更新資料庫,成功後,讓快取失效.為什麼不是寫完資料庫後更新快取?主要是怕兩個併發的寫操作導致髒資料。
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,獲取資料,尚未返回
- 寫操作進來,更新資料來源,invalidate快取
- 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
相關文章
- Cache Aside PatternIDE
- oracle cache快取Oracle快取
- 微軟BI 之SSIS 系列 - Lookup 元件的使用與它的幾種快取模式 - Full Cache, Partial Cache, NO Cache...微軟元件快取模式
- Guava學習:Cache快取Guava快取
- Spring Cache快取框架Spring快取框架
- Spring Cache快取註解Spring快取
- 快取融合(Cache Fusion)介紹快取
- SpringBoot系列——cache快取Spring Boot快取
- 淺聊HTTP快取 (HTTP Cache)HTTP快取
- 全棧必備:快取cache全棧快取
- 全棧必備——快取Cache全棧快取
- Spring Boot Cache Redis快取Spring BootRedis快取
- HTTP請求的快取(Cache)機制HTTP快取
- oracle result cache 結果集快取的使用Oracle快取
- 查詢快取(query_cache)的影響快取
- 【構建Android快取模組】(二)Memory Cache & File CacheAndroid快取
- LRU cache快取簡單實現快取
- localStorage和SessionStorage,Application,Cache快取SessionAPP快取
- Mysql 查詢快取 query_cacheMySql快取
- 快取模式概要快取模式
- Python 的快取機制: functools.lru_cachePython快取
- 解鎖快取新姿勢——更靈活的 Cache快取
- 注意 Laravel 清除快取 PHP artisan cache:clear 的使用Laravel快取PHP
- Java中常用快取Cache機制的實現Java快取
- JAVA 拾遺 — CPU Cache 與快取行Java快取
- 寫Cache快取物件測試例項快取物件
- Linux系統下清空Cache快取Linux快取
- ansible 開啟facts_cache快取快取
- mybatis一級快取(session cache)引發的問題MyBatis快取Session
- 註釋驅動的 Spring cache 快取介紹Spring快取
- Java快取機制:Ehcache與Guava Cache的比較Java快取Guava
- Django 頁面快取的cache_key是如何生成的Django快取
- HTTP快取——304與200 from cacheHTTP快取
- 在Buffer Cache中自動大表快取快取
- 如何配置極狐GitLab Runner Cache 快取Gitlab快取
- Cache與資料庫的一致性(快取更新模式和操作順序)資料庫快取模式
- 快取頭Cache-Control的含義和使用快取
- 使用快取(Cache)的幾種方式,回顧一下~~~快取