跟开涛学架构六【应用级缓存】

栏目: Java · 发布时间: 6年前

内容简介:跟开涛学架构六【应用级缓存】
  1. 基于空间:指缓存设置了存储空间,如果设置为10MB,当达到存储空间上限时,按照一定的策略移除数据。
  2. 基于容量:指缓存设置了最大大小,当缓存的条目超过最大大小时,按照一定的策略移除旧数据。
  3. 基于时间
    TTL(Time To Live):存活期,即缓存数据从创建开始直到到期的一个时间段。
    TTI(Time To Idle):空闲期,即缓存数据多久没有被访问后移除缓存的时间。
  4. 基于 Java 对象引用
    软引用:如果一个对象是软引用,那么当JVM堆内存不足时,垃圾回收器可以回收这些对象。
    弱引用:当垃圾回收器回收内存时,如果发现弱引用,则将它立即回收。相对于软引用,弱引用有更短的生命周期。
    注意:只有在没有其他强引用对象引用软引用/弱引用对象时,垃圾回收时才会回收该引用。
  5. 回收算法
    使用基于空间和基于容量的缓存会使用一定的策略移除旧数据,常见如下:
    FIFO(First In First Out):先进先出算法,即先放入的缓存先被删除。
    LRU(Least Recently Used):最近最少使用算法,使用时间距离现在最久的被移除。
    LFU(Lease Frequently Used):最不常用算法,一定时间段内使用频率最少的呗移除。

Java缓存类型

  • 堆缓存:使用java堆内存来存储对象,好处是不需要序列化/反序列化,速度快,缺点是受GC影响。可以使用Guava Cache、Ehcache 3.x、MapDB实现。
  • 堆外缓存:缓存数据存储在堆外,突破了JVM的枷锁,读取数据时需要序列化/反序列化,比对堆内缓存慢很多。可以使用Ehcache 3.x、MapDB实现。
  • 磁盘缓存:在JVM重启时数据还在,而堆缓存/堆外缓存数据会丢失,需要重新加载。可以使用Ehcache 3.x、MapDB实现。
  • 分布式缓存:没啥好说的了,Redis…

对于上述缓存类型的使用,可以采用存储最热的数据到堆缓存,相对热的数据到堆外缓存,全量数据到分布式缓存。下面就来看下每一种类型怎么使用,示例写在real_server_1项目中。

堆缓存

<!-- guava版本 -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>

private static void guavaHeap() throws InterruptedException {
    Cache<String, String> myCache = CacheBuilder.newBuilder()
            .concurrencyLevel(4) // 并发级别,即ConcurrentHashMap segment数量,越大并发能力越强
            .expireAfterWrite(10, TimeUnit.SECONDS) // 设置过期TTL
            .maximumSize(10000) // 设置缓存的容量,当超出时,按照LRU进行回收
            .build();
    myCache.put("guava", "heap_guava");

    while (true) {
        String value = myCache.getIfPresent("guava");
        if (value == null) {
            System.out.println("Cache expired");
            return;
        } else {
            System.out.println(value);
        }
        Thread.sleep(1000);
    }
}

堆外缓存

<!-- mapdb -->
<dependency>
    <groupId>org.mapdb</groupId>
    <artifactId>mapdb</artifactId>
    <version>3.0.5</version>
</dependency>
        
private static void mapdbDirect() throws InterruptedException {
   HTreeMap myCache = DBMaker.memoryDirectDB()
           .concurrencyScale(16)
           .make().hashMap("myCache")
           .expireStoreSize(64 * 1024 * 1024)
           .expireMaxSize(10000)
           .expireAfterCreate(10, TimeUnit.SECONDS)
           .expireAfterUpdate(10, TimeUnit.SECONDS)
           .expireAfterGet(10, TimeUnit.SECONDS)
           .create();

    myCache.put("mapdb", "direct_mapdb");
    System.out.println(myCache.get("mapdb"));
    Thread.sleep(15000);
    System.out.println(myCache.get("mapdb"));
}

磁盘缓存

private static void mapdbDisk(){
    DB db = DBMaker.fileDB("/Users/zhangjing/mpdb.data")
            .fileMmapEnable() // 启用mmap
            .fileMmapEnableIfSupported() // 在支持的平台上启用mmap
            .fileMmapPreclearDisable() // 让mmap更快
            .cleanerHackEnable() // 一些BUG处理
            .transactionEnable() // 启用事务
            .closeOnJvmShutdown()
            .concurrencyScale(16).make();

    HTreeMap myCache = db.hashMap("myCache")
            .expireMaxSize(10000)
            .expireAfterCreate(10, TimeUnit.SECONDS)
            .expireAfterUpdate(10, TimeUnit.SECONDS)
            .expireAfterGet(10, TimeUnit.SECONDS)
            .createOrOpen();

    myCache.put("mapdb", "disk_mapdb");
    db.commit();
}

分布式缓存

参考Redis

上面简单地过下集中缓存的皮毛,在具体使用过程中还得逐个深入研究。

缓存使用模式实践

已经有前人给我们总结了模式的使用场景,主要分为两大类:Cache-Aside和Cache-As-SoR(Read-through、Write-through、Write-behind)。首先介绍三个名词:

  • SoR(system-of-record):记录系统,或者可以叫做数据源,即实际存储原始数据的系统。
  • Cache:缓存,是SoR快照数据,Cache的访问速度比SoR要快,放入Cache的目的是提升访问速度,减少回源到SoR的次数。
  • 回源:即回到数据源头获取数据,Cache没有命中时,需要从SoR读取数据,这叫做回源。

Cache-Aside

Cache-Aside即业务代码围绕着Cache写,是由业务代码直接维护缓存:

  • 读场景:先从缓存获取数据,如果没有命中,则回源到SoR并将源数据放入缓存供下次读取使用。
  • 写场景:先将数据写入SoR,写入成功后立即将数据同步写入缓存;或者先将数据写入SoR,写入成功后将缓存数据过期,下次读取时再加载缓存。

Cache-Aside可以用AOP模式去实现。

Cache-As-SoR

Cache-As-SoR即把Cache看做SoR,所有操作都是对Cache进行,然后Cache再委托给SoR进行真是的读/写。即业务代码中只看到Cache的操作,看不到关于SoR的相关代码。有三种实现:Read-Through、Write-Through、Write-Behind。

Read-Through

Read-through,业务代码首先调用Cache,如果Cache不命中由Cache回源到SoR,而不是业务代码(即由Cache读SoR)。使用Read-through模式,需要配置一个CacheLoader组件用来回源SoR加载源数据。

Write-Through

Write-Through被称为穿透写模式/直写模式,业务代码首先调用Cache写(新增/修改)数据,然后由Cache负责写缓存和写SoR,而不是由业务代码。使用Write-Through模式需要配置一个CacheWriter组件来回写SoR。

Write-Behind

Write-Behind也叫做 Write-Back,我们称之为回写模式。不同于Write-Through是同步写SoR和Cache,Write-Behind是异步写。异步之后可以实现批量写、合并写、延时写和限流。

这部分代码就不贴出来了,可以选择适合自己的缓存方式再深入研究。


以上所述就是小编给大家介绍的《跟开涛学架构六【应用级缓存】》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构C++语言描述

数据结构C++语言描述

William Ford,William Topp / 刘卫东 沈官林 / 清华大学出版社 / 1999-09-01 / 58.00

一起来看看 《数据结构C++语言描述》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换