package com.github.believe3301.nonheapdb;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Cache提供比MemoryManager更上层的服务
* MemoryManager添加等操作还有和底层的Record交互.
* Cache就只和key,value交互.不需要知道底层的数据结构
*/
public class DBCache {
private MemoryManager manager;
private BucketManager bm;
private Metrics metrics;
private long maxbytes;
private int blocksize;
public DBCache() {
this(16);
}
public DBCache(int hashpower) {
this(hashpower, Util.Mb(32));
}
public DBCache(int hashpower, int blocksize) {
this(hashpower, blocksize, -1);
}
public DBCache(int hashpower, int blocksize, long maxbytes) {
this.blocksize = Util.align(blocksize, 1024);
if (this.maxbytes < 0) {
this.maxbytes = -1;
} else {
this.maxbytes = Util.align(maxbytes, this.blocksize);
}
this.metrics = new Metrics();
this.bm = new BucketManager(hashpower);
this.manager = new MemoryManager(bm, this.blocksize, this.maxbytes);
}
public boolean exist(String key) {
return this.manager.existRecord(key);
}
public boolean put(String key, byte[] value) {
this.metrics.incrPutCmds();
if (value.length > blocksize) {
this.metrics.incrPutFails();
return false;
}
boolean ret = false;
if (ret = this.manager.put(key, value)) {
this.metrics.incrRecords();
} else {
this.metrics.incrPutFails();
}
return ret;
}
public byte[] get(String key) {
Record rec = this.manager.getRecord(key);
if (rec != null) {
this.metrics.incrGetHits();
return rec.getData();
}
this.metrics.incrGetMisses();
return null;
}
public void remove(String key) {
this.metrics.incrRemoveCmds();
if (!this.manager.removeRecord(key)) {
this.metrics.incrRemoveFails();
}
}
public String info() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("get_totals: %d\r\n", this.metrics.getRecords()));
sb.append(String.format("get_hits: %d\r\n", this.metrics.getHits()));
sb.append(String.format("get_misses: %d\r\n", this.metrics.getMisses()));
sb.append(String.format("put_cmds: %d\r\n", this.metrics.getPutCmds()));
sb.append(String.format("put_fails: %d\r\n", this.metrics.getPutFails()));
sb.append(String.format("remove_cmds: %d\r\n", this.metrics.getRemoveCmds()));
sb.append(String.format("remove_fails: %d\r\n", this.metrics.getRemoveFails()));
sb.append(String.format("current_records: %d\r\n", this.manager.reccount()));
sb.append(String.format("allocated_bytes: %d\r\n", this.manager.allocated()));
sb.append(String.format("used_bytes: %d\r\n", this.manager.used()));
sb.append(String.format("fp_size: %d\r\n", this.manager.fpsize()));
sb.append(String.format("defragment_count: %d\r\n", this.manager.dfcount()));
return sb.toString();
}
//不考虑hash冲突的Map桶策略
public static class BucketManager {
private int capacity;
private long[] buckets;
public BucketManager(int hashpower) {
this.capacity = 1 << hashpower;
this.buckets = new long[this.capacity];
}
//根据key获取在哪个buckets桶里
private int getPos(String key) {
int h = key.hashCode();
//0x7FFFFFFF=0111 1111 | 1111 1111 | 1111 1111 | 1111 1111
//key进行hash之后是int类型=4个字节=4*8=32位
//为什么最高位不是F,而是7? 因为这个数字就是Integer.MAX_VALUE
return (h & 0x7FFFFFFF) % capacity;
}
public long getBucket(String key) {
return buckets[getPos(key)];
}
public void setBucket(String key, long index) {
//会不会有冲突呢? getPos(key)对于不同的key可能会落到同一个bucket中
//index是什么值: RecordIndex.getBucket()的值.
//实际上代表了RecordIndex这个对象! 它封装了一条记录的index(在哪个内存块里),capacity(容量),offset(在内存块里的偏移量)
//解决冲突的方式是通过Record parent字段. --> MemeoryManager.getRecord()调用了Record.setParent
//setBucket保存的是最近放入的key的RecordIndex.getBucket()的值. 如果这个bucket先前有数据,也会覆盖.
buckets[getPos(key)] = index;
}
}
//解决有冲突的buckets. 使用二维数组
static class BucketManager2 {
private long[][] segments;
private int capacity;
private final ReentrantReadWriteLock[] segmentLocks = new ReentrantReadWriteLock[16];
{
for (int i = 0; i < 16; i++)
segmentLocks[i] = new ReentrantReadWriteLock();
}
public BucketManager2(int hashpower) {
hashpower = Math.max(hashpower, 12);
this.capacity = 1 << (hashpower - 4);
this.segments = new long[16][];
}
public long getBucket(String key) {
int h = key.hashCode();
int segment = h >>> 28;
ReentrantReadWriteLock l = segmentLocks[segment];
try {
l.readLock().lock();
long[] buckets = segments[segment];
if (buckets != null) {
int pos = (h & 0x0FFFFFFF) % capacity;
return buckets[pos];
}
return 0L;
} finally {
l.readLock().unlock();
}
}
public void setBucket(String key, long index) {
int h = key.hashCode();
int segment = h >>> 28;
ReentrantReadWriteLock l = segmentLocks[segment];
try {
l.writeLock().lock();
long[] buckets = segments[segment];
if (buckets == null) {
buckets = new long[this.capacity];
segments[segment] = buckets;
}
int pos = (h & 0x0FFFFFFF) % capacity;
buckets[pos] = index;
} finally {
l.writeLock().unlock();
}
}
}
static class Metrics {
private AtomicInteger gethits;
private AtomicInteger getmisses;
private AtomicInteger putcmds;
private AtomicInteger putfails;
private AtomicInteger removecmds;
private AtomicInteger removefails;
private AtomicInteger rectotals;
public Metrics() {
this.gethits = new AtomicInteger();
this.getmisses = new AtomicInteger();
this.putcmds = new AtomicInteger();
this.putfails = new AtomicInteger();
this.removecmds = new AtomicInteger();
this.removefails = new AtomicInteger();
this.rectotals = new AtomicInteger();
}
public int incrGetHits() {
return this.gethits.addAndGet(1);
}
public int getHits() {
return this.gethits.get();
}
public int incrGetMisses() {
return this.getmisses.addAndGet(1);
}
public int getMisses() {
return this.getmisses.get();
}
public int incrPutCmds() {
return this.putcmds.addAndGet(1);
}
public int getPutCmds() {
return this.putcmds.get();
}
public int incrPutFails() {
return this.putfails.addAndGet(1);
}
public int getPutFails() {
return this.putfails.get();
}
public int incrRemoveCmds() {
return this.removecmds.addAndGet(1);
}
public int getRemoveCmds() {
return this.removecmds.get();
}
public int incrRemoveFails() {
return this.removefails.addAndGet(1);
}
public int getRemoveFails() {
return this.removefails.get();
}
public int incrRecords() {
return this.rectotals.addAndGet(1);
}
public int getRecords() {
return this.rectotals.get();
}
}
}