适用于高并发的本地缓存方案
程序员文章站
2022-03-04 15:00:42
...
使用本地缓存需要注意两个问题:
1 内存管理,及时解除无用对象的引用。防止大量无用对象进入old区,引发full gc。
2 数据同步,如果应用是一个集群,需要保持各台机器的数据一致性。
问题1的解决可以采用LRU算法,预先定好缓存大小。达到最大值后,清除最近最少使用的对象。
问题2比较复杂,需要有一个集中的地方控制缓存一致,比如可以采用消息中间件,写时进行异步复制。这种方式成本较大。
其实对于业务系统,用户通过浏览器访问的数据,不需要很强的一致性。只要在3秒内,各台机器能够保持最终一致,用户是感觉不大不同机器数据的不同步。
因此通过控制缓存时间为3秒或其它很短的时间,就可以保证一定程度的数据一致,避免数据同步的开销和复杂度。
缓存时间短又会引发另外一个问题,就是缓存的命中率。在高并发访问下,缓存时间短可能会导致,大量的访问刚好都没有命中,缓存穿透给系统带来瞬间的高峰压力。
综合考虑以上几个矛盾点,可以对缓存数据进行封装,使用有效失效次数的缓存对象,保证在高并发情况下,大量的访问都能命中缓存,同时又能保证缓存及时失效和更新。代码如下
首先是LRUHashMap。需要注意的是LRUHashMap不是线程安全的,非线程安全访问是否会带来问题,取决于业务场景,对共享变量进行相互覆盖的影响(参考http://www.iteye.com/topic/656670)。
class LRUHashMap extends LinkedHashMap<String, Object> {
private int MAX_ENTRIES;
public LRUHashMap(int size) {
MAX_ENTRIES = size;
}
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return size() > MAX_ENTRIES;
}
}
缓存对象
public class CacheObject implements Serializable {
long createTime;
long lifeTime;
Object value;
AtomicInteger invalidTimes;
public CacheObject(long lifeTime, Object value) {
this.lifeTime = lifeTime;
this.value = value;
invalidTimes = new AtomicInteger(0);
this.createTime = System.currentTimeMillis();
}
public long getCreateTime() {
return createTime;
}
public void setCreateTime(long createTime) {
this.createTime = createTime;
}
public long getLifeTime() {
return lifeTime;
}
public void setLifeTime(long lifeTime) {
this.lifeTime = lifeTime;
}
public Object getValue() {
// 如果已经失效很久,说明很久没有被访问,那么直接返回null,不对失效次数进行判断。
if ((System.currentTimeMillis() - createTime) > 10 * lifeTime) {
return null;
}
// 保证在高并发下,缓存失效也可以保证较高的命中率
if (System.currentTimeMillis() - createTime > lifeTime) {
if (invalidTimes.incrementAndGet() > 3) {
return value;
} else {
return null;
}
}
return value;
}
public void setValue(Object value) {
this.value = value;
}
public AtomicInteger getInvalidTimes() {
return invalidTimes;
}
public void setInvalidTimes(AtomicInteger invalidTimes) {
this.invalidTimes = invalidTimes;
}
}
本地缓存
public class LocalCache {
LRUHashMap cacheArea = new LRUHashMap(20);
public Object get(String key) {
CacheObject cacheObject = (CacheObject) cacheArea.get(key);
return cacheObject == null ? null : cacheObject.getValue();
}
public void put(String key, Object value, long lifeTime) {
CacheObject cacheObject = new CacheObject(lifeTime, value);
cacheArea.put(key, cacheObject);
}
}
参考http://www.cnblogs.com/redcreen/archive/2011/02/15/1955248.html