一、概念
引入缓存,主要用于实现系统的高性能,高并发。将数据库查询出来的数据放入缓存服务中,因为缓存是存储在内存中的,内存的读写性能远超磁盘的读写性能,所以访问的速度非常快。但是电脑重启后,内存中的数据会全部清除,而磁盘中的数据虽然读写性能很差,但是数据不会丢失。
二、手写本地缓存
首先创建一个缓存实体类
package ;
import ;
/**
* @author qx
* @date 2023/7/27
* @des 自定义缓存实体类
*/
@Data
public class MyCache {
/**
* 键
*/
private String key;
/**
* 值
*/
private Object value;
/**
* 过期时间
*/
private Long expireTime;
}
接着我们编写一个缓存操作的工具类
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author qx
* @date 2023/7/27
* @des 自定义本地缓存工具类
*/
public class CacheUtil {
/**
* 缓存数据Map
*/
private static final Map<String, MyCache> CACHE_MAP = new ConcurrentHashMap<>();
/**
* 定时器线程池,用于清除过期缓存
*/
private static final ScheduledExecutorService executorService = ();
static {
// 注册一个定时线程任务,服务启动1秒之后,每隔500毫秒执行一次
// 定时清理过期缓存
(CacheUtil::clearCache, 1000, 500, );
}
/**
* 添加缓存
*
* @param key 缓存键
* @param value 缓存值
* @param expire 过期时间,单位秒
*/
public static void put(String key, Object value, long expire) {
MyCache myCache = new MyCache();
(key);
(value);
if (expire > 0) {
long expireTime = () + (expire).toMillis();
(expireTime);
}
CACHE_MAP.put(key, myCache);
}
/**
* 获取缓存
*
* @param key 缓存键
* @return 缓存数据
*/
public static Object get(String key) {
if (CACHE_MAP.containsKey(key)) {
return CACHE_MAP.get(key).getValue();
}
return null;
}
/**
* 移除缓存
*
* @param key 缓存键
*/
public static void remove(String key) {
CACHE_MAP.remove(key);
}
/**
* 清理过期的缓存数据
*/
private static void clearCache() {
if (CACHE_MAP.size() <= 0) {
return;
}
// 判断是否过期 过期就从缓存Map删除这个元素
CACHE_MAP.entrySet().removeIf(entry -> ().getExpireTime() != null && ().getExpireTime() > ());
}
}
最后,我们来测试一下缓存服务
package ;
import ;
import ;
import ;
import ;
@SpringBootTest
class VueSpringBootApplicationTests {
@Test
void contextLoads() throws InterruptedException {
// 写入缓存数据 2秒后过期
("name", "qx", 2);
Object value1 = ("name");
("第一次查询结果:" + value1);
// 停顿3秒
(3);
Object value2 = ("name");
("第二次查询结果:" + value2);
}
}
启动测试,我们从控制台的返回看到输出结果和我们的预期一致!
第一次查询结果:qx
第二次查询结果:null
实现思路其实很简单,采用ConcurrentHashMap
作为缓存数据存储服务,然后开启一个定时调度,每隔500
毫秒检查一下过期的缓存数据,然后清除掉!
三、基于Guava Cache实现本地缓存
相比自己编写的缓存服务,Guava Cache 要强大的多,支持很多特性如下:
-
支持最大容量限制
-
支持两种过期删除策略(插入时间和读取时间)
-
支持简单的统计功能
-
基于 LRU 算法实现
1.添加gugva的依赖
<!--guava-->
<dependency>
<groupId></groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
2.测试
@Test
void testGuava() throws ExecutionException, InterruptedException {
// 创建一个缓存实例
Cache<String, String> cache = ()
// 初始容量
.initialCapacity(5)
// 最大缓存数,超出淘汰
.maximumSize(10)
// 过期时间 设置写入3秒后过期
.expireAfterWrite(3, )
.build();
// 写入缓存数据
("name", "qq");
// 读取缓存数据
String value1 = ("name", () -> "key过期");
("第一次查询结果:" + value1);
// 停顿4秒
(4);
// 读取缓存数据
String value2 = ("name", () -> "key过期");
("第二次查询结果:" + value2);
}
启动测试,我们从控制台的返回看到输出结果和我们的预期一致!
第一次查询结果:qq
第二次查询结果:key过期
四、基于 Caffeine 实现本地缓存
Caffeine 是基于 java8 实现的新一代缓存工具,缓存性能接近理论最优,可以看作是 Guava Cache 的增强版,功能上两者类似,不同的是 Caffeine 采用了一种结合 LRU、LFU 优点的算法:W-TinyLFU,在性能上有明显的优越性。
1.引入Caffeine
<!--caffeine-->
<dependency>
<groupId></groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
2.测试
@Test
void testCaffeine() throws InterruptedException {
// 创建一个缓存实例
Cache<String, String> cache = ()
// 初始容量
.initialCapacity(5)
// 最大缓存数,超出淘汰
.maximumSize(10)
// 设置缓存写入间隔多久过期
.expireAfterWrite(3, )
// 设置缓存最后访问后间隔多久淘汰,实际很少用到
.build();
// 写入缓存数据
("userName", "张三");
// 读取缓存数据
String value1 = ("userName", (key) -> {
// 如果key不存在,会执行回调方法
return "key已过期";
});
("第一次查询结果:" + value1);
// 停顿4秒
(4000);
// 读取缓存数据
String value2 = ("userName", (key) -> {
// 如果key不存在,会执行回调方法
return "key已过期";
});
("第二次查询结果:" + value2);
}
输出结果:
第一次查询结果:张三
第二次查询结果:key已过期
五、基于 Encache 实现本地缓存
1.引入ehcache依赖
<!--ehcache-->
<dependency>
<groupId></groupId>
<artifactId>ehcache</artifactId>
<version>3.9.7</version>
</dependency>
2.自定义过期策略实现
package ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author qx
* @date 2023/7/27
* @des 自定义过期策略实现
*/
public class CustomExpiryPolicy<K, V> implements ExpiryPolicy<K, V> {
private final Map<K, Duration> keyExpireMap = new ConcurrentHashMap();
public Duration setExpire(K key, Duration duration) {
return (key, duration);
}
public Duration getExpireByKey(K key) {
return ((key))
.orElse(null);
}
public Duration removeExpire(K key) {
return (key);
}
@Override
public Duration getExpiryForCreation(K key, V value) {
return (getExpireByKey(key))
.orElse((Long.MAX_VALUE));
}
@Override
public Duration getExpiryForAccess(K key, Supplier<? extends V> value) {
return getExpireByKey(key);
}
@Override
public Duration getExpiryForUpdate(K key, Supplier<? extends V> oldValue, V newValue) {
return getExpireByKey(key);
}
}
3.测试
package ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @author qx
* @date 2023/7/27
* @des 测试Encache
*/
public class EncacheTest {
public static void main(String[] args) throws InterruptedException {
String userCache = "userCache";
// 自定义过期策略
CustomExpiryPolicy<Object, Object> customExpiryPolicy = new CustomExpiryPolicy<>();
// 声明一个容量为20的堆内缓存配置
CacheConfigurationBuilder configurationBuilder = CacheConfigurationBuilder
.newCacheConfigurationBuilder(, , (20))
.withExpiry(customExpiryPolicy);
// 初始化一个缓存管理器
CacheManager cacheManager = ()
// 创建cache实例
.withCache(userCache, configurationBuilder)
.build(true);
// 获取cache实例
Cache<String, String> cache = (userCache, , );
// 获取过期策略
CustomExpiryPolicy expiryPolicy = (CustomExpiryPolicy) ().getExpiryPolicy();
// 写入缓存数据
("userName", "张三");
// 设置3秒过期
("userName", (3));
// 读取缓存数据
String value1 = ("userName");
("第一次查询结果:" + value1);
// 停顿4秒
(4000);
// 读取缓存数据
String value2 = ("userName");
("第二次查询结果:" + value2);
}
}
输出结果:
第一次查询结果:张三
第二次查询结果:null