本文主要讲述下缓存的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又从新触发了一次,而后脏数据就清除了