目录
③ 基于FIFO(先进先出算法)设计缓存对象的淘汰策略,代码如下.
1.缓存篇
1.1 缓存介绍:
1.1 .1缓存是什么?
缓存是内存中存在的一个对象,可以是Map.collection等类型,利用这个对象可以对某些数据对象进行存储.对于数据库而言,这个对象也可以看作是一个临时的数据库,里面包含着实际数据库中的部分数据.
1.1.2 为什么要应用缓存?
在不使用缓存时,用户每次用浏览器访问某网站获取某项服务时,网站必须从数据库中进行相应的数据增删改查的工作,流程如下
用户的每一次请求都需要走一遍完整的过程,而当访问数量较多的时候,服务器会达到访问瓶颈,此时再进行访问时,该访问请求要么放入请求队列进行等候,要么作为无效请求.这样的结果就是系统效率低,用户体验差,造成用户流失......
而如果使用缓存,则可以大幅度降低数据的访问量.例如浏览器的本地缓存,可以缓存用户访问过的网页的html.js,image等文件,当用户再次访问时,可以直接从本地客户端获取这些文件,不需要从服务端进行请求. 例如服务器的本地缓存和分布式缓存,都可以作为存储部分数据库数据的"小型临时数据库",当web服务器需要访问数据库获取数据时,则可以先从缓存中查询,有则直接返回结果,没有则再查询数据库获取结果(如果有分布式缓存还会查分布式缓存,之后再查实际数据库).
1.1.3 使用缓存有什么优势和劣势?
使用缓存的优势总结来讲就是两点,①提高系统功能效率②减少数据库压力.
使用缓存有优势就必然有劣势和缺点,上面提到,对于服务器缓存来讲,它就是一个"临时的小型数据库","小型"也意味着会存储部分数据,需要消耗空间资源,是一种空间换时间的做法(一般来讲是值得的),"临时"则意味着缓存的数据是经常变换更新的,并且由于缓存中只是一部分数据库的信息,这就可能会造成一些问题,这些问题大致可以分为三种,缓存穿透,缓存击穿,缓存雪崩.这三个问题此处不进行详述
1.2 缓存模拟
1.2.1 缓存的基础构建
此处用HashMap作为缓存对象进行实现.
①缓存接口设计
public interface Cache {
void putObject(Object key,Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int size();
}
②简易缓存实现
public class PerpetualCache implements Cache{
private HashMap<Object,Object> cache=new HashMap<Object,Object>();
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
return "PerpetualCache{" +
"cache=" + cache.toString() +
'}';
}
}
③ 基于FIFO(先进先出算法)设计缓存对象的淘汰策略,代码如下.
//FIFO-Cache实现
package com.cache;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
public class FifoCache implements Cache{
/**关联Cache对象-找到可以存储数据的基层对象*/
private Cache cache;
/**定义Cache的最大容量*/
private int maxCap;
/**通过队列记录key的添加顺序*/
private Queue<Object> queue;
public FifoCache(Cache cache, int maxCap) {
this.cache = cache;
this.maxCap = maxCap;
this.queue=new LinkedList<Object>();
}
@Override
public void putObject(Object key, Object value) {
//1.记录key的顺序(加入队列)
queue.add(key);
//2.判断容器是否已满,满了则移除
if(queue.size()>maxCap){
Object firstKey = queue.remove();
//从cache中清除指定key对应的对象
cache.removeObject(firstKey);
}
//3.向cache添加数据
cache.putObject(key, value);
}
@Override
public Object getObject(Object key) {
return cache.getObject(key);
}
@Override
public Object removeObject(Object key) {
//1.从Cache中移除对象
Object object = cache.removeObject(key);
//2.从队列移除key
queue.remove(key);
return object;
}
@Override
public void clear() {
cache.clear();
queue.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
return "FifoCache{" +
"cache=" + cache.toString() +
", maxCap=" + maxCap +
", queue=" + queue +
'}';
}
public static void main(String[] args) {
Cache cache=new FifoCache(new PerpetualCache(), 3);
cache.putObject("胡桃", 1);
cache.putObject("钟离", 2);
cache.putObject("甘雨", 3);
cache.putObject("影", 4);
cache.putObject("凝光", 5);
System.out.println(cache);
}
}
基于FIFO进行实现,依次插入胡桃,钟离,甘雨,影,凝光五组值,由于限制大小为3,所以map会将前面插入的胡桃和钟离移除,最终剩下甘雨,影,凝光.(因为HashMap是无序的,所以cache中的值与queue中的值的序列不同)
④ 基于LRU(最近最少访问)算法
public class LruCache implements Cache{
private Cache cache;
/**
* 通过此属性记录要移除的数据对象
*/
private Object eldestKey;
private int maxcap;
/**
* 通过此 map 记录 key 的访问顺序
*/
private Map<Object, Object> keyMap;
@SuppressWarnings("serial")
public LruCache(final Cache cache, int maxCap) {
this.cache = cache; //LinkedHashMap 可以记录 key 的添加顺序或者访问顺序
this.maxcap=maxCap;
this.keyMap = new LinkedHashMap<Object, Object>(maxCap, 0.75f, true) {//accessOrder // 此方法每次执行 keyMap 的 put 操作时调用
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<Object, Object> eldest) {
boolean isFull =cache.size()>maxcap;
if (isFull) eldestKey = eldest.getKey();
return isFull;
}
};
}
@Override
public void putObject(Object key, Object value) { // 存储数据对象
cache.putObject(key, value); // 记录 key 的访问顺序,假如已经满了,就要从 cache 中移除数据
keyMap.put(key, key);// 此时会执行 keyMap 对象的 removeEldestEntry
if(eldestKey!=null) {
cache.removeObject(eldestKey);
eldestKey = null;
}
}
@Override
public Object getObject(Object key) {
keyMap.get(key);// 记录 key 的访问顺序
return cache.getObject(key);
}
@Override
public Object removeObject(Object key) {
return cache.removeObject(key);
}
@Override
public void clear() {
cache.clear();
keyMap.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
return cache.toString();
}
public static void main(String[] args) {
LruCache cache = new LruCache(new PerpetualCache(), 3);
cache.putObject("胡桃", 1);
cache.putObject("钟离", 2);
cache.putObject("甘雨", 3);
cache.getObject("钟离");
cache.getObject("胡桃");
cache.putObject("影", 4);
cache.putObject("凝光", 5);
System.out.println(cache);
}
}
由于最后访问了"胡桃"和"钟离",所以在进行淘汰时,优先淘汰最后访问的数据.
⑤Caffeine缓存的实现
这种实现比较复杂,是在spring框架中实现的,需要引入额外的依赖
<dependencies> <!--SpringBoot依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.3.2.RELEASE</version> </dependency> <!--本地缓存caffeine--> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> </dependencies>
先写配置类
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public CaffeineCacheManager caffeineCacheManager(){
//1.构建缓存对象
Caffeine caffeine = Caffeine.newBuilder()
.initialCapacity(1) //初始大小
.maximumSize(3) //最大大小
.expireAfterWrite(1, TimeUnit.HOURS); //写入/更新之后1小时过期
//构建缓存管理器对象
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setAllowNullValues(true);
caffeineCacheManager.setCaffeine(caffeine);
return caffeineCacheManager;
}
}
控制层
@RestController
public class CaffeineController {
@Autowired
CaffeineCacheManager caffeineCacheManager;
static int count=1;
@Cacheable(cacheManager = "caffeineCacheManager", value = "caffeine")
@GetMapping("/caffeine/{id}")
public Object selectById(@PathVariable("id") String id) {
System.out.println("模拟数据库获取数据"+count++);
//返回的结果会自动存入caffeineCache中
return "结果"+count;
}
}
启动项目,通过.http进行测试
执行测试
GET http://localhost:8080/caffeine/1 ### GET http://localhost:8080/caffeine/2 ### GET http://localhost:8080/caffeine/3 #### GET http://localhost:8080/caffeine/2 #### GET http://localhost:8080/caffeine/4 #### GET http://localhost:8080/caffeine/1 ####
结果如下,在第二次执行GET http://localhost:8080/caffeine/1请求时,由于容量设置为3,所以之前的数据被移除,只能再重新从数据库中获取.实际的Caffeine由于数据量较少,无法测出准确的性能