前言
说道缓存,大家想到的是一定是Redis,确实在国内Redis被大量应用,推上了新的高度!但是不一定所有的场合都要使用Redis,例如服务器资源紧缺,集成不方便的时候就可以考虑使用本地缓存。
简介
缓存应该是每个系统都要考虑的架构,缓存不仅可以加速系统的访问速度还可以提升系统的性能。如我们需要经常访问的高频热点数据,如果把它缓存起来就能有效减少数据库服务器的压力。手机验证码等有一定的失效时间,我们就可以考虑使用缓存,等失效时间过了,就删掉验证码。因此市面上缓存组件也层出不进,常见的有
- JCache:Java缓存API。由JSR107定义,定义了5个核心接口,分别是CachingProvider,CacheManager,Cache,Entry和Expriy
- EhCache:纯Java的进程内缓存框架,jvm虚拟机中缓存、速度快,效率高,是Hibernate中默认的CacheProvider,但是共享缓存与集群分布式应用整合不方便
- Redis:生态完善,通过socket访问缓存服务,效率上是比EhCache低的,但是在集群模式、分布式应用上就比较成熟,是大型应用首先中间件
- Caffeine:Caffeine是使用Java8对Guava缓存的重写版本,有人称它为缓存之王
优点
- 快速
- 简单
- 多种缓存策略(设置有效期等)
- 缓存数据有两级:内存和磁盘,因此无需担心容量问题
- 缓存数据会在虚拟机重启的过程中写入磁盘
- 可以通过RMI、可插入API等方式进行分布式缓存
- 具有缓存和缓存管理器的侦听接口
- 支持多缓存管理器实例,以及一个实例的多个缓存区域
- 提供Hibernate的缓存实现
jar
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.10.6</version>
</dependency>
本质
就是简单看下SpringBoot关于缓存处理源码是怎么写的。说到底就是上面所说AOP思想,通过动态代理实现,目标方法让代理对象去调用,调用之前先看下缓存有没有,如果有,则从缓存上获取结果并直接返回。如果缓存中没有则执行目标方法,并把方法执行结果缓存。
CacheManager->Cache->Element
Ehcache.xml
默认会加载calsspath下的ehcache.xml,加载失败会加载Ehcache.jar下的ehcache-failsafe.xml
<cache name="account"eternal="false"diskPersistent="false"maxElementsInMemory="10000"timeToIdleSeconds="120"timeToLiveSeconds="120"memoryStoreEvictionPolicy="LRU"></cache><cache name="smsCache"eternal="false"diskPersistent="false"maxElementsInMemory="1000"timeToIdleSeconds="60"timeToLiveSeconds="600"memoryStoreEvictionPolicy="LRU"></cache><!--
name 缓存名称
eternal true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
timeToIdleSeconds 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态
timeToLiveSeconds 设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义
maxElementsInMemory 内存中最大缓存对象数;maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行
memoryStoreEvictionPolicy 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)
maxElementsOnDisk 硬盘中最大缓存对象数,若是0表示无穷大
overflowToDisk 是否保存到磁盘,当系统宕机时
diskPersistent 是否缓存虚拟机重启期数据,是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache名称,后缀名为index的文件,这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存,要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskExpiryThreadIntervalSeconds 磁盘失效线程运行时间间隔,默认为120秒
clearOnFlush 内存数量最大时是否清除
-->
策略
Ehcache 提供了多种缓存策略,可以根据实际需求选择合适的策略。其中,最常用的包括:
- LRU(Least Recently Used):移除最近最少使用的缓存项。(时间)
- LFU(Least Frequently Used):移除最不经常使用的缓存项。(次数)
- FIFO(First In, First Out):先进先出,即移除最早加入的缓存项。
- TTL(Time To Live):根据缓存项的过期时间来判断是否要移除该项。
随机替换:随机选择一项进行移除。
缓存注解-自动
JCache(JSR-107)的注解去大大简化我们的开发。
相关注解和概念 | 说明 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedistCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
JSR-107缓存注解使用
光知道SpringBoot集成Ehcache还不够,我们还要知道怎么使用。然而ehcache在实际应用中可以抽离出来单独使用,但是需要自己手动实例化与put数据,一般不推荐。推荐配合JSR-107缓存注解去使用(因为Spring都给我们封装好了),Spring的缓存功能丰富,它还提供了很多注解,去完成不同的场景~其中使用最多的是@Cacheable注解。
@Cacheable (使用最多)
/*
@Cacheable几个属性:1.cacheNames/value:缓存组件的名字 2.key:指定缓存数据使用的key,默认使用方法参数的值,可以编写SpEL进行指定,如#id就是参数的值3.keyGenerator:key的生成器,可以自己指定key的生成器的组件id,使用时key/keyGenerator只能二选一4.cacheManager:指定缓存管理器,或者cacheResolver指定获取解析器5.condition:指定复合条件的情况下才缓存,如condition = "#id > 0"6.unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,如 unless = "#result == null"7.sync:是否启用异步模式,如果启用unless就不支持了。默认为false8.缓存的值就是方法返回的结果
*/
// 使用示例@Override@Cacheable(value = "account",key = "#id")public Account getAccountById(int id) {final Account account = accountDao.selectById(id);return Objects.requireNonNull(account);}
@CachePut
即调用方法,又更新缓存数据。如修改了某个数据库的某个数据,同时更新缓存
执行过程:
- 先调用目标方法
- 将目标方法的结果缓存起来
value:命名空间
key: 主键
@CachePut(value = "account",key = "#id")public Account getAccountById(int id) {final Account account = accountDao.selectById(id);return Objects.requireNonNull(account);}
@CacheEvict
缓存清除,一般在删除数据方法中使用
@CacheEvict(value = "account",key = "#id")public boolean deleteAccountById(int id) {return accountDao.deleteById(id) > 0;}
CacheManager-手动
getCache
put
get
remove
@Autowiredprivate CacheManager cacheManager;@Overridepublic String sendSms(String phoneNumber) {final String uuid = UUID.randomUUID().toString().substring(0, 4);cacheManager.getCache("smsCache").put(new Element(phoneNumber, uuid));return uuid;}@Overridepublic boolean checkSms(String phoneNumber, String code) {final Cache smsCache = cacheManager.getCache("smsCache");smsCache.final Element element = smsCache.get(phoneNumber);final String smscode = (String) element.getObjectValue();return code.equals(smscode);}