侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

Ehcache缓存学习笔记

林贤钦
2020-12-10 / 0 评论 / 0 点赞 / 584 阅读 / 9,294 字
温馨提示:
本文最后更新于 2021-03-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Ehcache缓存学习笔记

《玩转EhCache之最简单的缓存框架》

《Ehcache官方文档》

《spring boot+Ehcache+gradle(本人整理的demo仓库)》

Ehcache简介

Ehcache是一个用Java实现的使用简单,高速,实现线程安全的缓存管理类库,ehcache提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案。同时ehcache作为开放源代码项目,采用限制比较宽松的Apache License V2.0作为授权方式,被广泛地用于Hibernate, Spring,Cocoon等其他开源系统。Ehcache 从 Hibernate 发展而来,逐渐涵盖了 Cahce 界的全部功能,是目前发展势头最好的一个项目。具有快速,简单,低消耗,依赖性小,扩展性强,支持对象或序列化缓存,支持缓存或元素的失效,提供 LRU、LFU 和 FIFO 缓存策略,支持内存缓存和磁盘缓存,分布式缓存机制等等特点。

Ehcache特色

够快:Ehcache的发行有一段时长了,经过几年的努力和不计其数的性能测试,Ehcache终被设计于large, high concurrency systems.

够简单:开发者提供的接口非常简单明了,从Ehcache的搭建到运用运行仅仅需要的是你宝贵的几分钟。其实很多开发者都不知道自己用在用Ehcache,Ehcache被广泛的运用于其他的开源项目。比如:hibernate

够袖珍:关于这点的特性,官方给了一个很可爱的名字small foot print ,一般Ehcache的发布版本不会到2M,V 2.2.3 才 668KB

够轻量:核心程序仅仅依赖slf4j这一个包,没有之一!

好扩展:Ehcache提供了对大数据的内存和硬盘的存储,最近版本允许多实例、保存对象高灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多

监听器:缓存管理器监听器 (CacheManagerListener)和 缓存监听器(CacheEvenListener),做一些统计或数据一致性广播挺好用的

Ehcache的重要概念

CacheManager、Cache、Element的关系

  • CacheManager:是缓存管理器,可以通过单例或者多例的方式创建,也是Ehcache的入口类。
  • Cache:每个CacheManager可以管理多个Cache,每个Cache可以采用hash的方式管理多个Element。
  • Element:用于存放真正缓存内容的

Ehcache的缓存数据淘汰策略

  • FIFO:先进先出
  • LFU:最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
  • LRU:最近最少使用,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

Ehcache的缓存数据过期策略

Ehcache采用的是懒淘汰机制,每次往缓存放入数据的时候,都会存一个时间,在读取的时候要和设置的时间做TTL比较来判断是否过期。

Ehcache缓存的使用

只给相关的核心代码,其余代码在gitee仓库,详细代码->https://gitee.com/linxianqin/springboot-bucket-gradle

gradle配置,需要让springboot缓存支持器。

    //Spring Boot 缓存支持启动器
    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-cache', version: '2.2.11.RELEASE'
    compile group: 'net.sf.ehcache', name: 'ehcache', version: '2.10.6' 

ehcache.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false" monitoring="autodetect"
         dynamicConfig="true">
    <diskStore path="java.io.tmpdir/ehcache"/>
    <defaultCache
            maxElementsInMemory="50000"
            eternal="false"
            timeToIdleSeconds="3600"
            timeToLiveSeconds="3600"
            overflowToDisk="true"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU" />

    <cache name="demo-cache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="3600"
           overflowToDisk="false"
           statistics="true"
           memoryStoreEvictionPolicy="LRU">
    </cache>
</ehcache>

application.yml文件配置加载xml文件

spring:
	cache:
        type: ehcache
        ehcache:
      		config: classpath:ehcache.xml

在启动器上开启缓存@EnableCaching,这样就完成了相关配置了。

@Service
@CacheConfig(cacheNames = {"demo-cache"})
public class AccountServiceImpl implements AccountService {
    private AccountMapper accountMapper;
    private CacheService cacheService;
    public AccountServiceImpl( AccountMapper accountMapper,CacheService cacheService){
        this.accountMapper=accountMapper;
        this.cacheService= cacheService;
    }

    /**
     *@Cacheable缓存key为account__+id数据到缓存Account中
     * @param id
     * @return
     */

    @Cacheable( key = "'account_'+ #id")
    @Override
    public AccountDTO findById(Long id){
        return AccountConvert.INSTANCE.entity2dto(accountMapper.selectById(id));
    }
    /**
     * @param id
     * @CacheEvict从缓存demo-cache中删除key为account_id的数据
     */
    @Transactional
    @CacheEvict(key = "'account_'+ #id")
    @Override
    public void remove(Long id) {
        accountMapper.deleteById(id);
    }

    /**
     * 保存数据,以及保存到缓存中
     * @param accountDTO
     */
    @Transactional
    @Override
    public void save(AccountDTO accountDTO) {
        Account account = AccountConvert.INSTANCE.dto2entity(accountDTO);
        accountMapper.insert(account);
        cacheService.put("account_"+accountDTO.getId(),AccountConvert.INSTANCE.entity2dto(account));//放入缓存中
    }

    /**
     * 更新数据以及更新缓存的数据
     * @param accountDTO
     */
    @Transactional
    @Override
    public void updateById(AccountDTO accountDTO) {
        Account account = AccountConvert.INSTANCE.dto2entity(accountDTO);
        accountMapper.updateById(account);
        cacheService.put("account_"+accountDTO.getId(),AccountConvert.INSTANCE.entity2dto(account));//修改缓存中
    }
}
@Service
public class CacheServiceImpl implements CacheService {
    @Override
    public void put(String key, Object value) {
        CacheUtils.put(key,value);
    }
}    
@Slf4j
public class CacheUtils
{

    private static CacheManager cacheManager = SpringUtil.getBean(CacheManager.class);

    private static final String SYS_CACHE = "demo-cache";

    /**
     * 获取demo-cache缓存
     * 
     * @param key
     * @return
     */
    public static Object get(String key)
    {
        return get(SYS_CACHE, key);
    }

    /**
     * 获取demo-cache缓存
     * 
     * @param key
     * @param defaultValue
     * @return
     */
    public static Object get(String key, Object defaultValue)
    {
        Object value = get(key);
        return value != null ? value : defaultValue;
    }

    /**
     * 写入demo-cache缓存
     * 
     * @param key
     * @return
     */
    public static void put(String key, Object value)
    {
        put(SYS_CACHE, key, value);
    }

    /**
     * 从demo-cache缓存中移除
     * 
     * @param key
     * @return
     */
    public static void remove(String key)
    {
        remove(SYS_CACHE, key);
    }

    /**
     * 获取缓存
     *
     * @param cacheName
     * @param key
     * @return
     */
    public static Object get(String cacheName, String key)
    {
        return getCache(cacheName).get(getKey(key));
    }

    /**
     * 获取缓存
     * 
     * @param cacheName
     * @param key
     * @param defaultValue
     * @return
     */
    public static Object get(String cacheName, String key, Object defaultValue)
    {
        Object value = get(cacheName, getKey(key));
        return value != null ? value : defaultValue;
    }

    /**
     * 写入缓存
     * 
     * @param cacheName
     * @param key
     * @param value
     */
    public static void put(String cacheName, String key, Object value)
    {
       getCache(cacheName).put(new Element(key, value));
    }

    /**
     * 从缓存中移除
     *
     * @param cacheName
     * @param key
     */
    public static void remove(String cacheName, String key)
    {
        getCache(cacheName).remove(getKey(key));
    }

    /**
     * 从缓存中移除所有
     * 
     * @param cacheName
     */
    public static void removeAll(String cacheName)
    {
        Cache cache = getCache(cacheName);
        List<String> keys = cache.getKeys();
        for (String key : keys) {
            cache.remove(key);
        }
        log.info("清理缓存: {} => {}", cacheName, keys);
    }

    /**
     * 从缓存中移除指定key
     * 
     * @param keys
     */
    public static void removeByKeys(Set<String> keys)
    {
        removeByKeys(SYS_CACHE, keys);
    }

    /**
     * 从缓存中移除指定key
     * 
     * @param cacheName
     * @param keys
     */
    public static void removeByKeys(String cacheName, Set<String> keys)
    {
        for (Iterator<String> it = keys.iterator(); it.hasNext();)
        {
            remove(it.next());
        }
        log.info("清理缓存: {} => {}", cacheName, keys);
    }

    /**
     * 获取缓存键名
     * 
     * @param key
     * @return
     */
    private static String getKey(String key)
    {
        return key;
    }

    /**
     * 获得一个Cache,没有则显示日志。
     * 
     * @param cacheName
     * @return
     */
    public static Cache getCache(String cacheName)
    {
        Cache cache = cacheManager.getCache(cacheName);
        if (cache == null)
        {
            throw new RuntimeException("当前系统中没有定义“" + cacheName + "”这个缓存。");
        }
        return cache;
    }

    /**
     * 获取所有缓存
     *
     * @return 缓存组
     */
    public static String[] getCacheNames()
    {
        return   cacheManager.getCacheNames();
    }
}
/**
 * 功能描述:确保应用退出时能关闭后台线程
 * @author lxq
 * @version 1.00
 * @Date 2020/12/8
 */
@Slf4j
@Component
public class ShutdownManager {
    private static CacheManager cacheManager = SpringUtil.getBean(CacheManager.class);
    @PreDestroy
    public void destroy()
    {
        shutdownEhCacheManager();
    }
    private void shutdownEhCacheManager()
    {
        try
        {
            log.info("====关闭缓存====");

            if (cacheManager != null)
            {
                cacheManager.shutdown();
            }
        }
        catch (Exception e)
        {
            log.error(e.getMessage(), e);
        }
    }
}

注意事项

  1. 如果要使用磁盘缓存,缓存的Element必须实现序列化接口。否则会抛出NotSerializableException异常。
  2. Ehcache有一个后台线程专门做Ellment失效监测以及清除工作。设置线程运行间隔时间,可通过设置diskExpiryThreadIntervalSeconds属性来完成,此值不宜设置过低,否则会导致清理线程占用大量CPU资源。默认值是120秒。
  3. 磁盘缓存大小默认是没有限制的,不过可通过maxElementsOnDisk来指定。当磁盘缓存达到maxElementsOnDisk指定的值时,Ehcache会清理磁盘中的缓存使用默认策略是LFU(使用频率最低)
  4. 在使用完Ehcache后,必须要shutdown缓存。Ehcache中有自己的关闭机制,不过最好在你的代码中显示调用CacheManager.getInstance().shutdown();
  5. Cache:对于getValue()能取到可序列化的值;getObjectValue()取得非序列化的值
  6. CacheManager可以通过单例(factory的静态方法)或者构造函数(constructors)创建。分别叫做single model和instance model。当两种情况都有的时候,系统会采用单例模式,构造器每次都生成单例模式

以下属性是可选的

  • timeToIdleSeconds - 缓存element在过期前的空闲时间。默认为0,表示可空闲无限时间. (如果指定了这个时间,是否在被hit的前超过了这个时间就会被remove?在内存缓存数目超限之前不会被remove)
  • timeToLiveSeconds - 缓存element的有效生命期。这个类似于timeouts,默认为0,不过期(是否通常情况下应该大于等于timeToIdleSeconds,小于会如何?idle时间也会减小和这个数值一样)
  • diskPersistent - 在VM重启的时候是否持久化磁盘缓存,默认是false。
  • diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒. (测试一下0的时候会如何)
  • memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候,移除缓存中element的策略。默认是LRU,可选的有LFU和FIFO可对缓存中的element配置诸如监听器和加载器
0

评论区