背景
- 由于项目中有一个看板需求,有很多个图表、指标数据展示,所以考虑用缓存,考虑本项目是个单节点部署,所以优先考虑使用本地缓存。
考虑
- 每个图表、指标都有多个筛选条件组合,所以考虑主子key缓存,首先考虑的是redis,其实不太适合,虽然有对应的数据结构(HASH),但是子key我需要是一个Object对象,而不是一个筛选条件的字符串拼接(而且redis对key的长度也是有要求的)。
实现逻辑
- 借鉴arraylist的扩容机制;
- 借鉴hashmap的存储机制,使用横向数组,纵向链表进行存储;
- 使用读写分段锁减少锁的粒度
- 原先hashmap 需要对key进行hash从而找到位置,但是在这里是直接让业务场景绑定指定的位置,比如标的的出险频率这个图表 直接指定使用index=1的位置,将不同的筛选条件都存在这个位置上。
代码实现
@Slf4j
public class Cache<K, V> implements Serializable {
private final static int DEFAULT_CAPACITY = 16;
private final static int MAX_CAPACITY = 128;
private final static int DEFAULT_EXPIRATION = 1000 * 60 * 5;
private static final long serialVersionUID = 7509771018891971331L;
private volatile Node<K, V>[] table;
@Getter
private final AtomicInteger size = new AtomicInteger(0);
@Getter
private volatile int capacity;
private final int expirationTime;
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final ConcurrentHashMap<Integer, ReentrantReadWriteLock> tabReadWriteLockMap;
public Cache() {
this(DEFAULT_CAPACITY, DEFAULT_EXPIRATION);
}
public Cache(int capacity, int expirationTime) {
if (capacity <= 0 || expirationTime <= 0) {
throw new IllegalArgumentException("The capacity and expirationTime param is illegal.");
}
this.capacity = Math.min(capacity, MAX_CAPACITY);
this.table = new Node[this.capacity];
this.expirationTime = expirationTime;
this.tabReadWriteLockMap = new ConcurrentHashMap<>(this.capacity);
for (int i = 0; i < this.capacity; i++) {
this.tabReadWriteLockMap.put(i, new ReentrantReadWriteLock());
}
new Thread(() -> {
try {
while (true) {
for (int i = 0; i < this.capacity; i++) {
getWriteLock(i).lock();
try {
Node<K, V> prev = null;
Node<K, V> current = table[i];
long now = System.currentTimeMillis();
while (current != null) {
if (current.getExpireTime() < now) {
if (prev == null) {
table[i] = current.next;
} else {
prev.next = current.next;
}
size.decrementAndGet();
} else {
prev = current;
}
current = current.next;
}
} finally {
getWriteLock(i).unlock();
}
}
Thread.sleep(1000 * 60);
}
} catch (InterruptedException e) {
log.error("清理线程被中断,停止运行。");
}
}).start();
}
private ReentrantReadWriteLock.WriteLock getWriteLock(int index) {
if (!tabReadWriteLockMap.containsKey(index)) {
if (index > this.capacity - 1) {
log.warn("索引越界了,需要扩容了,使用全局锁。");
return readWriteLock.writeLock();
}
}
return tabReadWriteLockMap.get(index).writeLock();
}
private ReentrantReadWriteLock.ReadLock getReadLock(int index) {
if (!tabReadWriteLockMap.containsKey(index)) {
if (index > this.capacity - 1) {
log.warn("索引越界了,需要扩容了,使用全局锁。");
return readWriteLock.readLock();
}
}
return tabReadWriteLockMap.get(index).readLock();
}
public void put(K k, V v, int index) {
ReentrantReadWriteLock.WriteLock writeLock = this.getWriteLock(index);
writeLock.lock();
try {
Node<K, V> n = new Node<>(k, v, null, System.currentTimeMillis() + expirationTime);
if (index >= this.capacity) {
grow();
}
Node<K, V> c = table[index];
if (c == null) {
table[index] = n;
} else {
Node<K, V> l = c.findLastNode();
l.setNext(n);
}
size.incrementAndGet();
} finally {
writeLock.unlock();
}
}
public V get(K k, int index) {
ReentrantReadWriteLock.ReadLock readLock = this.getReadLock(index);
readLock.lock();
try {
if (index >= this.capacity) {
return null;
}
Node<K, V> current = table[index];
while (current != null) {
if (current.getKey().equals(k)) {
if (current.getExpireTime() < System.currentTimeMillis()) {
return null;
}
return current.getValue();
}
current = current.getNext();
}
return null;
} finally {
readLock.unlock();
}
}
public void grow() {
readWriteLock.writeLock().lock();
try {
if (this.capacity == MAX_CAPACITY) {
throw new BaseException("The capacity max length 128");
}
int oldCapacity = this.capacity;
this.capacity = Math.min(this.capacity + (this.capacity >> 1), MAX_CAPACITY);
Node<K, V>[] newTab = new Node[this.capacity];
System.arraycopy(table, 0, newTab, 0, table.length);
table = newTab;
for (int i = oldCapacity; i < this.capacity; i++) {
this.tabReadWriteLockMap.put(oldCapacity, new ReentrantReadWriteLock());
}
} finally {
readWriteLock.writeLock().unlock();
}
}
@Getter
@Setter
private static class Node<K, V> {
K key;
V value;
Node<K, V> next;
long expireTime;
public Node(K key, V value, Node<K, V> next, long expireTime) {
this.key = key;
this.value = value;
this.next = next;
this.expireTime = expireTime;
}
private synchronized Node<K, V> findLastNode() {
Node<K, V> l = this;
while (l.getNext() != null) {
l = l.getNext();
}
return l;
}
}
}