资讯专栏INFORMATION COLUMN

缓存的Cache Aside模式

paulli3 / 2520人阅读

摘要:序本文主要讲述下缓存的模式。更新是先更新数据库,成功后,让缓存失效为什么不是写完数据库后更新缓存主要是怕两个并发的写操作导致脏数据。

本文主要讲述下缓存的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
        
        
            com.github.ben-manes.caffeine
            caffeine
            2.5.5
        
        
        
            com.google.guava
            guava
            22.0
        
代码复现

这里使用代码复现一下这个脏数据场景。

读操作进来,发现没有cache,则触发loading,获取数据,尚未返回

写操作进来,更新数据源,invalidate缓存

loading获取的旧数据返回,cache里头存的是脏数据

@Test
    public void testCacheDirty() throws InterruptedException, ExecutionException {
        AtomicReference db = new AtomicReference<>(1);

        LoadingCache cache = CacheBuilder.newBuilder()
                .build(
                new CacheLoader() {
                    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 db = new AtomicReference<>(1);

        com.github.benmanes.caffeine.cache.LoadingCache 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

缓存更新的套路

caffeine: Java 8高性能缓存库包

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/61877.html

相关文章

  • (讨论)缓存同步、如何保证缓存一致性、缓存误用

    摘要:总结允许的缓存写场景大部分情况,修改成本会高于增加一次,因此应该淘汰缓存如果还在纠结,总是淘汰缓存,问题也不大先操作数据库,还是先操作缓存这里分了两种观点,的观点沈老师的观点。这里我觉得沈老师可能忽略了并发的问题,比如说以下情况一个写请求 缓存误用 缓存,是互联网分层架构中,非常重要的一个部分,通常用它来降低数据库压力,提升系统整体性能,缩短访问时间。 有架构师说缓存是万金油,哪里有问...

    msup 评论0 收藏0
  • (讨论)缓存同步、如何保证缓存一致性、缓存误用

    摘要:总结允许的缓存写场景大部分情况,修改成本会高于增加一次,因此应该淘汰缓存如果还在纠结,总是淘汰缓存,问题也不大先操作数据库,还是先操作缓存这里分了两种观点,的观点沈老师的观点。这里我觉得沈老师可能忽略了并发的问题,比如说以下情况一个写请求 缓存误用 缓存,是互联网分层架构中,非常重要的一个部分,通常用它来降低数据库压力,提升系统整体性能,缩短访问时间。 有架构师说缓存是万金油,哪里有问...

    y1chuan 评论0 收藏0
  • 知识整理之HTML篇

    摘要:最近关注的重学前端系列文章,也想把已知的前端知识体系梳理一遍,夯实基础的同时,总结提升。标准模式的排版和运作模式都是以该浏览器支持的最高标准运行。模式是目前最常用的模式。严格模式不允许展示型弃用元素和框架集。中空格不会被自动删除。 最近关注winter的重学前端系列文章,也想把已知的前端知识体系梳理一遍,夯实基础的同时,总结提升。接下来会从HTML、CSS、JS、性能、网络安全、框架通...

    yck 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<