言成言成啊 | Kit Chen's Blog

实现一个轻量易用缓存

发布于2022-06-12 15:58:26,更新于2022-07-03 08:41:34,标签:java  转载随意,文章会持续修订,请注明来源地址:https://meethigher.top/blog

生活很平淡,但是却很充实。想看的动漫很多、想写的代码很多,然后经常有时间不够用的感觉,惭愧惭愧。

真希望自己是个时间管理大师啊。

最近b站老给我推送纪晓岚和珅的视频,我就一直刷,然后趁着周末就追了个大半,正好最近也在写点东西,便记录一下。

写点东西,发现要引入缓存,但是只是一个单应用功能,redis部署麻烦且更吃内存,hazelcast是个不错的选择直接集成到项目但也是偏重量一点。

说白了,他们都是为了分布式而存在的,对于单节点的意义都是重量级的。便自己实现一套轻量缓存,好处嘛

  1. 轻量、开箱即用
  2. 基本单节点数据存储功能。基于map,lru策略、过时失效策略。

一、实现

缓存model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package top.meethigher.cachestore.model;

import java.io.Serializable;

public class CacheWrapper<V> implements Serializable {

/**
* 缓存数据
*/
private V data;

/**
* 过期时间
*/
private Long expireTs;

/**
* 创建时间
*/
private Long createTs;

public V getData() {
return data;
}

public void setData(V data) {
this.data = data;
}

public Long getExpireTs() {
return expireTs;
}

public void setExpireTs(Long expireTs) {
this.expireTs = expireTs;
}

public Long getCreateTs() {
return createTs;
}

public void setCreateTs(Long createTs) {
this.createTs = createTs;
}
}

下面实现了两种缓存,一种是基于ConcurrentHashMap,一种是基于LinkedHashMap

基于ConcurrentHashMap的普通缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package top.meethigher.cachestore.cache.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.meethigher.cachestore.model.CacheWrapper;
import top.meethigher.cachestore.utils.AssertUtil;

import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
* 默认内存中缓存存储实现
*
* @author chenchuancheng github.com/meethigher
* @since 2022/6/4 21:59
*/
public class DefaultInMemoryCacheStore<K,V> extends AbstractCacheStore<K, V> {

private final Timer timer;

/**
* 单位毫秒
*/
private final long PERIOD = 1000;


private final Logger log = LoggerFactory.getLogger(DefaultInMemoryCacheStore.class);

private final ConcurrentHashMap<K, CacheWrapper<V>> cacheMap = new ConcurrentHashMap<>();


private final ReentrantLock lock = new ReentrantLock();

@SuppressWarnings("all")
public DefaultInMemoryCacheStore() {
this.timer = new Timer("cache-expire-cleaner");
timer.scheduleAtFixedRate(new CacheExpireCleaner(), 0, PERIOD);
}

@Override
public void delete(K key) {
AssertUtil.notEmpty(key, "[delete] Cache key不能为空");
cacheMap.remove(key);
}

@Override
public LinkedHashMap<K, V> toMap() {
LinkedHashMap<K, V> map = new LinkedHashMap<>();
cacheMap.forEach((k, v) -> {
map.put(k, v.getData());
});
return map;
}

@Override
public Optional<CacheWrapper<V>> getInternal(K key) {
AssertUtil.notEmpty(key, "[getInternal] Cache key不能为空");
return Optional.ofNullable(cacheMap.get(key));
}

@Override
public void putInternal(K key, CacheWrapper<V> cacheWrapper) {
AssertUtil.notEmpty(key, "[putInternal] Cache key不能为空");
cacheMap.put(key, cacheWrapper);
}

@Override
public boolean putInternalIfAbsent(K key, CacheWrapper<V> cacheWrapper) {
AssertUtil.notEmpty(key, "[putInternalIfAbsent] Cache key不能为空");
AssertUtil.notEmpty(cacheWrapper, "[putInternalIfAbsent] Cache value不能为空");
lock.lock();
try {
Optional<V> valueOptional = get(key);
if (valueOptional.isPresent()) {
log.info("[putInternalIfAbsent] 缓存中已经存在[{}], 不可重复存储", key);
return false;
}
putInternal(key, cacheWrapper);
return true;

} finally {
lock.unlock();
}
}


@Override
public void clear() {
cacheMap.clear();
}

private class CacheExpireCleaner extends TimerTask {
@Override
public void run() {
cacheMap.keySet().forEach(x -> {
if (!DefaultInMemoryCacheStore.this.get(x).isPresent()) {
log.info("缓存[{}]已过期", x);
}
});
}
}


}

基于LinkedHashMap的LRU策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package top.meethigher.cachestore.cache.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.meethigher.cachestore.model.CacheWrapper;
import top.meethigher.cachestore.utils.AssertUtil;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* Least Recently Used
* 最近最少使用缓存优先淘汰
*
* @author chenchuancheng
* @since 2022/6/10 15:58
*/
public class LruInMemoryCacheStore<K, V> extends AbstractCacheStore<K, V> {

private final Logger log = LoggerFactory.getLogger(LruInMemoryCacheStore.class);
/**
* 定时器
*/
private final Timer timer;

/**
* 单位毫秒
*/
private final long PERIOD = 1000;


/**
* 读写锁
*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


/**
* 读锁
*/
private final Lock readLock = readWriteLock.readLock();

/**
* 写锁
*/
private final Lock writeLock = readWriteLock.writeLock();


/**
* lruCacheMap
*/
private final LruMap cacheMap;


public LruInMemoryCacheStore() {
//最大容量为int的最大值
this(1 << 30);
}

@SuppressWarnings("all")
public LruInMemoryCacheStore(int maxCapacity) {
cacheMap = new LruMap(maxCapacity);
this.timer = new Timer("lru-cache-expire-cleaner");
timer.scheduleAtFixedRate(new LruInMemoryCacheStore.LruCacheExpireCleaner(), 0, PERIOD);
}

@Override
public Optional<CacheWrapper<V>> getInternal(K key) {
readLock.lock();
try {
AssertUtil.notEmpty(key, "[getInternal] Cache key不能为空");
return Optional.ofNullable(cacheMap.get(key));
} finally {
readLock.unlock();
}
}

@Override
public void putInternal(K key, CacheWrapper<V> cacheWrapper) {
writeLock.lock();
try {
AssertUtil.notEmpty(key, "[putInternal] Cache key不能为空");
cacheMap.put(key, cacheWrapper);
} finally {
writeLock.unlock();
}
}

@Override
public boolean putInternalIfAbsent(K key, CacheWrapper<V> cacheWrapper) {
AssertUtil.notEmpty(key, "[putInternalIfAbsent] Cache key不能为空");
AssertUtil.notEmpty(cacheWrapper, "[putInternalIfAbsent] Cache value不能为空");
writeLock.lock();
try {
Optional<V> valueOptional = get(key);
if (valueOptional.isPresent()) {
log.info("[putInternalIfAbsent] 缓存中已经存在[{}], 不可重复存储", key);
return false;
}
putInternal(key, cacheWrapper);
return true;

} finally {
writeLock.unlock();
}
}

@Override
public void delete(K key) {
writeLock.lock();
try {
AssertUtil.notEmpty(key, "[delete] Cache key不能为空");
cacheMap.remove(key);
} finally {
writeLock.unlock();
}
}

@Override
public LinkedHashMap<K, V> toMap() {
readLock.lock();
try {
LinkedHashMap<K, V> map = new LinkedHashMap<>();
cacheMap.forEach((k, v) -> {
map.put(k, v.getData());
});
return map;
} finally {
readLock.unlock();
}
}

@Override
public void clear() {
writeLock.lock();
try {
cacheMap.clear();
} finally {
writeLock.unlock();
}
}

private class LruCacheExpireCleaner extends TimerTask {
@Override
public void run() {
writeLock.lock();
try {
//注意不能在map遍历时删除,所以此处将keySet另存,而不是直接使用keySet
Set<K> keySet = new HashSet<>(cacheMap.keySet());
for (K key : keySet) {
if (!LruInMemoryCacheStore.this.get(key).isPresent()) {
log.info("缓存[{}]已过期", key);
}
}
} finally {
writeLock.unlock();
}

}
}

private class LruMap extends LinkedHashMap<K, CacheWrapper<V>> {

private final int maxCapacity;

public LruMap(int maxCapacity) {
this.maxCapacity = maxCapacity;
}

@Override
protected boolean removeEldestEntry(Map.Entry<K, CacheWrapper<V>> eldest) {
log.info("队列中最不常用的数据key {}", eldest.getKey());
return this.size() > maxCapacity;
}
}
}

二、致谢

我的源码meethigher/cache-store: java实现的轻量级缓存,支持缓存的过期失效

平时逛逛github,发现的别人的好代码,顶层接口和思想直接借用了halo,这里面好代码好多啊。

  1. lishuo9527/LocalCache: JAVA LocalCache – JAVA 本地缓存工具类
  2. halo-dev/halo: ✍ 一款现代化的开源博客 / CMS 系统。
发布:2022-06-12 15:58:26
修改:2022-07-03 08:41:34
链接:https://meethigher.top/blog/2022/cache-store/
标签:java 
付款码 打赏 分享
shift+ctrl+1可控制目录显示