2019独角兽企业重金招聘Python工程师标准>>>
引言
一般来说,应用中的数据主要来自于数据库和缓存,现有的缓存中间件已经能满足大多数场景,但是有些场景更适合做服务器本地缓存,这就需要自己去实现了,这里给出了方攀的一个LocalCache的例子,并已在应用中得到了很好的应用。
关键词介绍
1、SoftReference
只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象,结合超时时间可以用来实现缓存。
2、ReferenceQueue
使得对象即使被GC了也可以持有ref,结合HashMap可以缩小键-值映射关系的数目。
实现原理
1、service类
public class CacheManager {
public interface ValueGenerator {
Object generate();
}
private final CacheUtil cache;
private final Lock lock = new ReentrantLock();
public CacheManager(int cacheSize, int expriedTime){
cache = new CacheUtil(true, cacheSize, expriedTime);
}
/**
* <pre>
* 返回 key 对应的 value,如果 value 已经超时,
* 则使用 ValueGenerator 产生新的 value,并返回。
* 使用此方法,可以避免多线程同时产生新 value 的问题。
* </pre>
*
* @param key
* @param generator
* @return
*/
public Object get(String key, ValueGenerator generator) {
Object result = cache.get(key);
if (result != null) {
return result;
}
try {
lock.lock();
result = cache.get(key);
if (result != null) {
return result;
}
Object value = generator.generate();
cache.put(key, value);
return value;
} finally {
lock.unlock();
}
}
}
2、cacheUtil
public class CacheUtil {
private final static float LOAD_FACTOR = 0.75f;
private final Map<Object, CacheEntry> cacheMap;
private int maxSize;
private long lifetime;
private final ReferenceQueue<Object> queue;
private final Lock lock = new ReentrantLock();
public CacheUtil(boolean soft, int maxSize){
this(soft, maxSize, 0);
}
public CacheUtil(boolean soft, int maxSize, int lifetime){
this.maxSize = maxSize;
this.lifetime = lifetime * 1000;
this.queue = soft ? new ReferenceQueue<Object>() : null;
int buckets = (int) (maxSize / LOAD_FACTOR) + 1;
cacheMap = new HashMap<Object, CacheEntry>(buckets, LOAD_FACTOR);
}
private void emptyQueue() {
if (queue == null) {
return;
}
while (true) {
CacheEntry entry = (CacheEntry) queue.poll();
if (entry == null) {
break;
}
Object key = entry.getKey();
if (key == null) {
continue;
}
CacheEntry currentEntry = cacheMap.remove(key);
if ((currentEntry != null) && (entry != currentEntry)) {
cacheMap.put(key, currentEntry);
}
}
}
private void expungeExpiredEntries() {
emptyQueue();
if (lifetime == 0) {
return;
}
int cnt = 0;
long time = System.currentTimeMillis();
for (Iterator<CacheEntry> t = cacheMap.values().iterator(); t.hasNext();) {
CacheEntry entry = t.next();
if (entry.isValid(time) == false) {
t.remove();
cnt++;
}
}
}
public int size() {
try {
lock.lock();
expungeExpiredEntries();
return cacheMap.size();
} finally {
lock.unlock();
}
}
public void clear() {
try {
lock.lock();
if (queue != null) {
for (CacheEntry entry : cacheMap.values()) {
entry.invalidate();
}
while (queue.poll() != null) {
}
}
cacheMap.clear();
} finally {
lock.unlock();
}
}
public void put(Object key, Object value) {
try {
lock.lock();
emptyQueue();
long expirationTime = (lifetime == 0) ? 0 : System.currentTimeMillis() + lifetime;
CacheEntry newEntry = newEntry(key, value, expirationTime, queue);
CacheEntry oldEntry = cacheMap.put(key, newEntry);
if (oldEntry != null) {
oldEntry.invalidate();
return;
}
if (maxSize > 0 && cacheMap.size() > maxSize) {
expungeExpiredEntries();
if (cacheMap.size() > maxSize) {
Iterator<CacheEntry> t = cacheMap.values().iterator();
CacheEntry lruEntry = t.next();
t.remove();
lruEntry.invalidate();
}
}
} finally {
lock.unlock();
}
}
public Object get(Object key) {
try {
lock.lock();
emptyQueue();
CacheEntry entry = cacheMap.get(key);
if (entry == null) {
return null;
}
long time = (lifetime == 0) ? 0 : System.currentTimeMillis();
if (entry.isValid(time) == false) {
cacheMap.remove(key);
return null;
}
return entry.getValue();
} finally {
lock.unlock();
}
}
public void remove(Object key) {
try {
lock.lock();
emptyQueue();
CacheEntry entry = cacheMap.remove(key);
if (entry != null) {
entry.invalidate();
}
} finally {
lock.unlock();
}
}
public void setCapacity(int size) {
try {
lock.lock();
expungeExpiredEntries();
if (size > 0 && cacheMap.size() > size) {
Iterator<CacheEntry> t = cacheMap.values().iterator();
for (int i = cacheMap.size() - size; i > 0; i--) {
CacheEntry lruEntry = t.next();
t.remove();
lruEntry.invalidate();
}
}
maxSize = size > 0 ? size : 0;
} finally {
lock.unlock();
}
}
public void setTimeout(int timeout) {
try {
lock.lock();
emptyQueue();
lifetime = timeout > 0 ? timeout * 1000L : 0L;
} finally {
lock.unlock();
}
}
protected CacheEntry newEntry(Object key, Object value, long expirationTime, ReferenceQueue<Object> queue) {
if (queue != null) {
return new SoftCacheEntry(key, value, expirationTime, queue);
} else {
return new HardCacheEntry(key, value, expirationTime);
}
}
3、cacheEntry(cacheUtil的内部类)
private static interface CacheEntry {
boolean isValid(long currentTime);
void invalidate();
Object getKey();
Object getValue();
}
private static class SoftCacheEntry extends SoftReference<Object> implements CacheEntry {
private Object key;
private long expirationTime;
SoftCacheEntry(Object key, Object value, long expirationTime, ReferenceQueue<Object> queue){
super(value, queue);
this.key = key;
this.expirationTime = expirationTime;
}
public Object getKey() {
return key;
}
public Object getValue() {
return get();
}
public boolean isValid(long currentTime) {
boolean valid = (currentTime <= expirationTime) && (get() != null);
if (valid == false) {
invalidate();
}
return valid;
}
public void invalidate() {
clear();
key = null;
expirationTime = -1;
}
}
应用接入
/** 初始化内存cache大小为256,过期时间为300即300秒 */
private static final CacheManager cacheManager = new CacheManager(256, 300);
public Map<Integer, DisplayArea<Course>> getCourseDisplayArea(final boolean withDetail, final int floorId) {
/* 注意key唯一性,需要加上withDetail */
return (Map<Integer, DisplayArea<Course>>) cacheManager.get(COURSE_CACHE_KEY_BY_FLOOR + floorId + withDetail,
new ValueGenerator() {
@Override
public Object generate() {
List<Integer> areaIds = DisplayAreaFeature.getCourseDisplayAreas(floorId);
Map<Integer, DisplayArea<Course>> displayAreas = getCourseDisplayArea(withDetail,
areaIds);
// 解析扩展属性 并填充标签
return fillTags(displayAreas,
parseExtProperty(displayAreas));
}
});
}