本地缓存应用文档

一、简介

缓存是存储在内存中的KV数据结构,分为分布式缓存和本地缓存。

分布式缓存:一般应用进程和缓存进程不在同一台服务器,通过远程调用进行通信,可以实现应用服务和缓存的完全解耦,支持大量的数据存储,

分布式缓存常见有redis,memcache等。

本地缓存:应用进程和缓存进程在同一个进程,没有网络开销,访问速度快,但受限于内存,不适合存储大量数据。本地缓存主要有Guava cache,Caffeine,Encache等。



二、压测对比(实测)

1、10W 级别 (put耗时,10线程)

a、caffeine,put,costTime=81ms,cpu=14.6% ,内存=15.4%

b、guava,put,costTime=194ms,cpu=20.7% ,内存=14.8%

c、ehcache,put,costTime=171ms, cpu=39.9% ,内存= 14.8%

2、10W 级别 (get耗时,10线程)


a、caffeine,get,costTime=17ms,cpu=10% ,内存=14.8%

b、guava,get,costTime=34ms,cpu=10% ,内存= 14.8%

c、ehcache,get,costTime=80ms,cpu=14.3% ,内存= 14.8%

3、100W 级别 (put耗时,10线程)


a、caffeine,put,costTime=354ms,cpu=21.6% ,内存=16.8%

b、guava,put,costTime=1396ms,cpu=47.7% ,内存=16.8%

c、ehcache,put,costTime=673ms,cpu=41.9% ,内存= 16.8%

4、100W 级别 (get耗时,10线程)


a、caffeine,get,costTime=154ms,cpu=12.3% ,内存=16.8%

b、guava,get,costTime=250ms,cpu=16.3% ,内存= 16.8%

c、ehcache,get,costTime=241ms,cpu=14.4% ,内存= 16.8%

5、1000W 级别 (put耗时,10线程)


a、caffeine,put,costTime=15618ms,cpu=189.0% ,内存= 31.6%

b、guava,put,costTime=25607ms,cpu=190.1% ,内存= 32.2%

c、ehcache,put,costTime=21425ms,cpu=193.1% ,内存= 32.2%

6、1000W 级别 (get耗时,10线程)


  a、caffeine,get,costTime=1939ms,cpu=73.3% ,内存= 32.2%

b、guava,get,costTime=2811ms,cpu=98.7% ,内存= 32.2%

c、ehcache,get,costTime=10027ms,cpu=98.7% ,内存= 32.2%

三、Guava Cache

1、maven引入

<dependency>

    <groupId>com.google.guava</groupId>

    <artifactId>guava</artifactId>

</dependency>

2、使用介绍

@Component

public class MyGuavaCache {

private LoadingCache<String, Field> fieldCache = CacheBuilder.newBuilder()

                      .maximumSize(1000) // 设置最大容量

                      .refreshAfterWrite(24, TimeUnit.HOURS) //设置过期刷新间隔

                      .build(

                          new CacheLoader<String, Field>() { // 这是自动刷新

                                @Override

                          public Field load(String key) throws Exception {

                                    return getFromDb(key);

                                      }

                              }

                          );


  /** * 从db取数 */

  private Field getFromDb(String key) {

      return dbRepositiory.getByKey(key);

  }

  /** * 提供给外部调用获取结果 */

  public Map<String, Field> get(String key) {

    try {

        return fieldCache.get(key); // 过期会自动执行load获取数据

    } catch (ExecutionException e) {

            throw new UncheckedExecutionException(e);

        }

    }

  /** *

  手动强制缓存失效

  * @param key

  */

  public void invalidate(String key) {

        fieldCache.invalidate(key);

    }


}

四、Caffeine

1、maven引入

<dependency>

    <groupId>com.github.ben-manes.caffeine</groupId>

    <artifactId>caffeine</artifactId>

</dependency>

2、使用介绍

1、手动加载

Cache<String, String> cache = Caffeine.newBuilder()

        //5分钟没有读写自动删除

        .expireAfterAccess(5, TimeUnit.MINUTES)

        //最大容量1024个,超过会自动清理空间

        .maximumSize(1024)

        .build();


String key = "key";

// 使用getIfPresent方法从缓存中获取值。如果缓存中不存指定的值,则方法将返回 null:

System.out.println(cache.getIfPresent(key));

// 也可以使用 get 方法获取值,该方法将一个参数为 key 的 Function 作为参数传入。如果缓存中不存在该 key, 则该函数将用于提供默认值,该值在计算后插入缓存中:

System.out.println(cache.get(key, new Function<Integer, Integer>() {

    @Override

    public Integer apply(Integer integer) {

        return 2;

    }

}));

2、同步加载

LoadingCache<String, String> cache = Caffeine.newBuilder()

        //5分钟没有读写自动删除

        .expireAfterAccess(5, TimeUnit.MINUTES)

        //最大容量1024个,超过会自动清理空间

        .maximumSize(1024)

        .build((newCacheLoader() {

                //默认的数据加载实现,当调用get取值的时候,

                //如果key没有对应的值,就调用这个方法进行加载

                @Override

                public String load(String key)  {

                    return"";           

                }       

            }

        );

3、异步加载

public static void main(String[] args) {

    AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine

    .newBuilder() .maximumSize(1000) .

    buildAsync(key -> slowMethod(key));

    CompletableFuture<String> g = loadingCache.get("test");

    String value = g.get();


}


static String slowMethod(String key) throws Exception {

    Thread.sleep(1000); return key + ".result";

}

4、过期时间设置

LoadingCache<String,String> cache = Caffeine.newBuilder()

//限制最大数量

.maximumSize(1000)

//基于权重

.maximumWeight(1000)

//指定计算权重的方式

.weigher(this::caculateWeight)

//缓存在写入多久后失效

.expireAfterWrite(1000,TimeUnit.SECONDS)

//缓存在访问多久后失效

.expireAfterAccess(1000,TimeUnit.SECONDS)

//多种时间过期策略组合使用

.expireAfter(new Expiry<String, String>() {


      public long expireAfterCreate(Key key, Graph graph, long currentTime) {

          //首次存入缓存后,过期时间为 4 秒

          return 4000000000L;

      }

      public long expireAfterUpdate(Key key,

                                Graph graph,long currentTime,

                                long currentDuration) {

            //更新key后,过期时间为 4 秒               

            return 4000000000L;

      }

      public long expireAfterRead(Key key,

                                  Graph graph,

                                  long currentTime,

                                  long currentDuration) {

            //每次被读取后,过期时间为 4 秒                     

            return 4000000000L;

      }

  })

  .build(this::load);

5、延迟刷新

refreshAfterWrite(long duration, TimeUnit unit)

写操作完成后多久才将数据刷新进缓存中,两个参数只是用于设置时间长短的。

只适用于 LoadingCache 和 AsyncLoadingCache,如果刷新操作没有完成,读取的数据只是旧数据。

6、异步清除、更新监听

当缓存中的数据发送更新,或者被清除时,就会触发监听器,在监听器里可以自定义一些处理手段,比如打印出哪个数据被清除,原因是什么。这个触发和监听的过程是异步的,就是说可能数据都被删除一小会儿了,监听器才监听到。

例子

MyStatsCounter myStatsCounter = new MyStatsCounter();

Caffeine caffeine = Caffeine.newBuilder()

        .maximumWeight(30)

        .removalListener((String key, Person value, RemovalCause cause)->{

            System.out.println("被清除人的年龄:" + value.getAge() + ";  清除的原因是:" + cause);

        })

        .weigher((String key, Person value)-> value.getAge());

Cache cache = caffeine.build();

cache.put("one", new Person(12, "one"));

cache.put("two", new Person(18, "two"));

cache.put("one", new Person(14, "one"));

cache.invalidate("one");

cache.put("three", new Person(31, "three"));

Thread.sleep(2000);

运行结果

被清除人的年龄:12;  清除的原因是:REPLACED

被清除人的年龄:14;  清除的原因是:EXPLICIT

被清除人的年龄:18;  清除的原因是:SIZE

EXPLICIT : 表示显式地调用删除操作,直接将某个数据删除。

REPLACED:表示某个数据被更新。

EXPIRED:表示因为生命周期结束(过期时间到了),而被清除。

SIZE:表示因为缓存空间大小受限,总权重受限,而被清除。

7、同步监听器

之前的 removalListener 是异步监听,此处的 writer 方法可以设置同步监听器,同步监听器一个实现了接口 CacheWriter 的实例化对象,我们需要自定义接口的实现类

关键是要实现 CacheWriter 接口的两个方法,当新增,更新某个数据时,会同步触发 write 方法的执行。当删除某个数据时,会触发 delete 方法的执行。

举个例子:

public class MyCacheWriter implements CacheWriter<String, Application.Person> {

    @Override

    public void write(String s, Application.Person person) {

        System.out.println("新增/更新了一个新数据:" + person.getName());

    }

    @Override

    public void delete(String s, Application.Person person,

                      RemovalCause removalCause) {

        System.out.println("删除了一个数据:" + person.getName());

    }

}

Caffeine caffeine = Caffeine.newBuilder()

        .maximumWeight(30)

        .writer(new MyCacheWriter())

        .weigher((String key, Person value)-> value.getAge());

Cache cache = caffeine.build();

cache.put("one", new Person(12, "one"));

cache.put("two", new Person(18, "two"));

cache.invalidate("two");

运行结果:

新增/更新了一个新数据:one

新增/更新了一个新数据:two

删除了一个数据:two

8、常用的方法

V getIfPresent(K key) :如果缓存中 key 存在,则获取 value,否则返回 null。

void put( K key, V value):存入一对数据 。

Map getAllPresent(Iterable var1) :参数是一个迭代器,表示可以批量查询缓存。

void putAll( Map var1): 批量存入缓存。

void invalidate(K var1):删除某个 key 对应的数据。

void invalidateAll(Iterable var1):批量删除数据。

void invalidateAll():清空缓存。

long estimatedSize():返回缓存中数据的个数。

CacheStats stats():返回缓存当前的状态指标集。

ConcurrentMap asMap():将缓存中所有的数据构成一个 map。

void cleanUp():会对缓存进行整体的清理,比如有一些数据过期了,但是并不会立马被清除,所以执行一次 cleanUp 方法,会对缓存进行一次检查,清除那些应该清除的数据。

V get( K var1, Function var2):第一个参数是想要获取的 key,第二个参数是函数

五、Encache

1、maven引入

        <dependency>

            <groupId>org.ehcache</groupId>

            <artifactId>ehcache</artifactId>

        </dependency>

2、使用介绍

static longsize=10000;

// 声明一个cacheBuilder

static CacheManagerehcacheManager = CacheManagerBuilder

.newCacheManagerBuilder()

                .withCache("encacheInstance", CacheConfigurationBuilder

                //声明一个容量为100000000的堆内缓存

.newCacheConfigurationBuilder(String.class,String.class,

ResourcePoolsBuilder.heap(100000000)))

                .build(true);

static org.ehcache.Cacheehcache =

ehcacheManager.getCache("encacheInstance", String.class, String.class);


for (int j = 0; j <size; j++) {

ehcache.put(i+j+"key",i+j+"value");

}

for (int j = 0; j <size; j++) {

ehcache.get(i+j+"key");

}

六、Spring boot 集成Caffeine

1、maven

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-cache</artifactId>

        </dependency>

        <dependency>

            <groupId>com.github.ben-manes.caffeine</groupId>

            <artifactId>caffeine</artifactId>

        </dependency>

2、配置文件

spring.cache.cache-names=people

spring.cache.caffeine.spec= initialCapacity=50,maximumSize=500,expireAfterWrite=20s,refreshAfterWrite=5s

3、启动类

package com.linlong;

import com.github.benmanes.caffeine.cache.CacheLoader;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.boot.CommandLineRunner;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cache.annotation.EnableCaching;

import org.springframework.context.annotation.Bean;

/**

* 启动类

* @author yehan

*/

@SpringBootApplication

@Slf4j

@EnableCaching// 开启缓存,需要显示的指定

public class Application implements CommandLineRunner {

    @Value("${spring.application.name}")

    private String applicationName;

    public static void main(String[] args) {

SpringApplication.run(Application.class, args);

    }

    @Override

    public void run(String... strings) throws Exception {

log.info("-------------------- [{}]初始化完成 --------------------", applicationName);

    }

/**

    * 必须要指定这个Bean,refreshAfterWrite这个配置属性才生效

    *

    * @return

    */

    @Bean

    public CacheLoader<Object, Object> cacheLoader() {

        CacheLoader<Object, Object> cacheLoader = new CacheLoader<Object, Object>() {

            @Override

            public Object load(Object key) throws Exception {

                return null;

            }

            // 重写这个方法将oldValue值返回回去,进而刷新缓存

            @Override

            public Object reload(Object key, Object oldValue) throws Exception {

                return oldValue;

            }

        };

        return cacheLoader;

    }

}

4、代码

package com.linlong.logic.service.impl;

import com.linlong.domain.vo.PeopleVO;

import com.linlong.logic.service.CaffeineService;

import com.linlong.repository.cache.PeopleMap;

import lombok.extern.log4j.Log4j2;

import org.springframework.cache.annotation.CacheEvict;

import org.springframework.cache.annotation.CachePut;

import org.springframework.cache.annotation.Cacheable;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import java.util.Map;

/**

*

* @author yehan

*/

@Service

@Log4j2

public class CaffeineServiceImpl implements CaffeineService {

    @Resource

    private PeopleMap peopleMap;


    @Cacheable(value = "people", key = "#id", sync = true)

    @Override

    public PeopleVO get(String id) {

        return peopleMap.get(id);

    }

    @Cacheable(value = "people")

    @Override

    public Map<String, PeopleVO> getAll() {

        return peopleMap.getAll();

    }

    @CachePut(value = "people", key = "#value.id")

    @Override

    public void put(PeopleVO value) {

        peopleMap.put(value.getId(),value);

    }

    @CacheEvict(value = "people",key = "#id")

    @Override

    public void delete(String id) {

        peopleMap.delete(id);

    }

}

6、配置参数

initialCapacity=[integer]: 初始的缓存空间大小

maximumSize=[long]: 缓存的最大条数

maximumWeight=[long]: 缓存的最大权重

weigher(Function函数):缓存权重

expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期

expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期

refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存

weakKeys: 打开key的弱引用

weakValues:打开value的弱引用

softValues:打开value的软引用

recordStats:开发统计功能

7、注意事项

expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。

maximumSize和maximumWeight不可以同时使用

weakValues和softValues不可以同时使用

8、DEMO

暂时无法在文档外展示此内容

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容