一、简介
缓存工作的原则,就是“引用的局部性”,这可以分为时间局部性和空间局部性。空间局部性是指CPU在某一时刻需要某个数据,那么很可能下一步就需要其附近的数据;时间局部性是指当某个数据被访问过一次之后,过不了多久时间就会被再一次访问。
常用缓存算法:
1.LRU算法
LRU(The Least Recently Used,最近最久未使用算法)是一种常见的缓存算法,如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最久没有访问的数据最先被置换(淘汰)。
2.LFU算法
LFU(Least Frequently Used ,最近最少使用算法)也是一种常见的缓存算法。
如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰。
3.FIFO算法
FIFO 算法是一种比较容易实现的算法。思想是先进先出(FIFO,队列),这是最简单、最公平的一种思想,即如果一个数据是最先进入的,那么可以认为在将来它被访问的可能性很小。空间满的时候,最先进入的数据会被最早置换(淘汰)掉。
二、Ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。
Ehcache 直接在jvm虚拟机中缓存,速度快,效率高;缓存数据有两级:内存和磁盘,因此无需担心容量问题但是缓存共享麻烦,集群分布式应用不方便。处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。
三、流程
spring 已将缓存领域统一,无论使用哪种缓存实现,不同的只是缓存配置,开发者使用的缓存注解都是一致的。
spring boot 可以非常方便使用ehcache,如果ehcache 依赖存在,并且classpath 下有一个ehcache.xml 配置,EhCacheManager 将自动作为缓存实现。
1.新建spring boot 项目,pom.xml 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
2.resources 下新建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">
<!-- 磁盘缓存位置 -->
<diskStore path="./ehcache"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="100"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120">
</defaultCache>
<!-- 配置一个default_cache 的缓存 -->
<cache name="default_cache"
maxEntriesLocalHeap="100"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120">
</cache>
</ehcache>
3.@SpringBootApplication 修饰的启动类添加 @EnableCaching 注解开启缓存配置
4.User.java
package com.vincent.po;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
5.UserService.java
package com.vincent.service;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.vincent.po.User;
@Service
public class UserService {
@Cacheable(cacheNames = {"default_cache"},key = "#id")
public User get(String id) {
User user = new User();
user.setName("Vincent");
user.setAge(26);
System.out.println("cache method...");
return user;
}
@CachePut(cacheNames = {"default_cache"},key = "#id")
public User update(String id){
User user = new User();
user.setName("Vincent-" + id);
user.setAge(26);
System.out.println("update method...");
return user;
}
@CacheEvict(cacheNames = {"default_cache"},key = "#id")
public void delete(String id) {
System.out.println("delete method...");
}
}
@Cacheable 表示对方法进行缓存,默认情况下缓存的key 是方法的参数,缓存的value 是方法的返回值,其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,如有则直接使用缓存数据,该方法不会执行,否则执行该方法并把返回值缓存起来
@CachePut 用于数据更新方法上,每次执行时都不会检查是否已有数据,而是直接执行方法,让后将执行结果缓存起来,如果该key对应的数据已经存在将会被覆盖
@CacheEvice 表示移除一个key 对应的缓存
6.TestController.java
package com.vincent.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.vincent.po.User;
import com.vincent.service.UserService;
@RestController
public class TestController {
@Autowired
private UserService userService;
@GetMapping("/test/add")
public User add(String id) {
return userService.get(id);
}
@GetMapping("/test/update")
public User update(String id) {
return userService.update(id);
}
@GetMapping("/test/del")
public void del(String id) {
userService.delete(id);
}
}
四、测试
1.访问 http://localhost:8080/test/add?id=test-cache
浏览器输出:
控制台输出:
多次刷新该接口控制台输出没有多余的 cache method… ,表面数据缓存成功
2.访问 http://localhost:8080/test/update?id=test-cache 更新数据
控制台输出如下:
再次刷新该接口控制台将再次执行该更新方法
3.访问 http://localhost:8080/test/del?id=test-cache
浏览器没有任何信息
控制台输出 delete method…
4.再次访问 http://localhost:8080/test/add?id=test-cache 缓存方法将被执行。
控制台输出 cache method…
五、总结
ehcache.xml 配置文件中需要配置缓存名字如:
<cache name="xxx" />
代码中对缓存的注解中需要指定 cacheNames 缓存名称,否制会抛异常,
查找相关异常代码如下
在使用ehcache 时需要指定缓存名字,否制会抛异常。ehcache.xml 配置中的defaultCache 就没什么作用,cache 配置的参数和 defaultCache 一样,不过多了一个name 属性。defaultCache 可能时给用户参考配置使用的。
cache 配置参数说明如下:
name : 缓存的名称,可以通过指定名称获取指定的某个Cache对象
maxElementsInMemory :内存中允许存储的最大的元素个数,0代表无限个
clearOnFlush:内存数量最大时是否清除。
eternal :设置缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。根据存储数据的不同,例如一些静态不变的数据如省市区等可以设置为永不过时
timeToIdleSeconds : 设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds :缓存数据的生存时间(TTL),也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
overflowToDisk :内存不足时,是否启用磁盘缓存。
maxEntriesLocalDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
maxElementsOnDisk:硬盘最大缓存个数。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskPersistent:是否在VM重启时存储硬盘的缓存数据。默认值是false。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
maxEntriesLocalHeap:是用来限制当前缓存在堆内存上所能保存的最大元素数量的。Ehcache规定如果在CacheManager上没有指定maxBytesLocalHeap时必须在各个Cache上指定maxBytesLocalHeap或者maxEntriesLocalHeap,但maxEntriesLocalHeap和maxBytesLocalHeap不能同时出现。也就是说我们不能在一个Cache上同时指定maxBytesLocalHeap和maxEntriesLocalHeap,也不能在Cache上指定maxEntriesLocalHeap的同时在CacheManager上指定maxBytesLocalHeap。但同时在CacheManager和Cache上指定maxBytesLocalHeap则是允许的。