/**
*
*/
package com.github.seanlinwang.tkv;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.github.seanlinwang.tkv.hdfs.HdfsIndexStore;
import org.apache.hadoop.fs.FileSystem;
import com.github.seanlinwang.tkv.hdfs.HdfsDataStore;
/**
* @author sean.wang
* @since Mar 7, 2012
*/
public class HdfsImpl implements Tkv {
//对于HDFS,不能用Map来映射key->Record的映射.而是用HDFS文件的形式来存储,即索引文件.
private HdfsIndexStore indexStore;
//数据也是用HDFS文件存储. 相比Local的实现,Record记录在磁盘文件中,key->Record的映射放在内存中.
//HDFS的实现是数据和索引都用文件来存储.
private HdfsDataStore dataStore;
private Lock writeLock = new ReentrantLock();
public HdfsImpl(FileSystem fs, File localDir,
String indexFilename, String dataFilename,
int keyLength, int tagLength) throws IOException {
//索引文件,先保存在本地一份
File localIndexFile = new File(localDir, indexFilename);
if (!localDir.exists()) {
boolean rs = localDir.mkdirs();
if (!rs) {
throw new IOException("can't create local dir!");
}
}
if (!localIndexFile.exists()) {
boolean rs = localIndexFile.createNewFile();
if (!rs) {
throw new IOException("can't create local index file!");
}
}
this.setIndexStore(new HdfsIndexStore(fs, indexFilename, localIndexFile, keyLength, tagLength));
this.setDataStore(new HdfsDataStore(fs, dataFilename));
}
public void setDataStore(HdfsDataStore dataStore) {
this.dataStore = dataStore;
}
public void setIndexStore(HdfsIndexStore indexStore) {
this.indexStore = indexStore;
}
public DataStore getDataStore() {
return dataStore;
}
public IndexStore getIndexStore() {
return indexStore;
}
// 1. 开始写:创建HDFS的dataStore,准备往hdfs文件中追加数据
public void startWrite() throws IOException {
this.dataStore.openOutput();
}
// 4. 结束写,将本地索引文件拷贝到HDFS上,这样索引文件在HDFS上
public void endWrite() throws IOException {
this.indexStore.flush();
this.dataStore.flushAndCloseOutput();
}
public void startRead() throws IOException {
this.dataStore.openInput();
}
public void endRead() throws IOException {
this.dataStore.closeInput();
}
private List<Meta> metas = new ArrayList<Meta>();
Map<String, Tag> lastTagHolder = new HashMap<String, Tag>();
@Override
public boolean put(String key, byte[] value) throws IOException {
return this.put(key, value, (String[]) null);
}
// 2. put追加数据到hdfs的数据文件中.同时要记录Meta信息加入到List metas中
@Override
public boolean put(String key, byte[] value, String... tagNames) throws IOException {
try {
this.writeLock.lock();
//索引文件中已经存在这个key,key不能重复!添加失败
//在buildIndex时会往indexStore中添加key
if (this.indexStore.getIndex(key) != null) {
return false; // this key already exists
}
//在写入数据之前,获取文件的当前位置,作为value的offset,会被保存在Meta里
long offset = this.dataStore.length();
//数据文件只保存value
this.dataStore.append(value);
//构建索引,put时只是放在内存的List<Meta>列表里
Meta meta = new Meta();
meta.setKey(key);
//offset是value在dataStore中的偏移量
meta.setOffset(offset);
meta.setLength(value.length);
// key, offset, valueLength is enough. why not value,
// because value is already store in dataStore on HDFS! so we don't need it in Meta
if (tagNames != null) {
for (String tagName : tagNames) {
meta.addTag(tagName);
}
}
metas.add(meta);
return true;
} finally {
this.writeLock.unlock();
}
}
// 3. 构建索引,将List metas追加到本地文件indexStore中
public void buildIndex() throws IOException {
try {
writeLock.lock();
if (metas.size() == 0) {
return;
}
//排序Meta里面的key
Collections.sort(metas, new Comparator<Meta>() {
@Override
public int compare(Meta o1, Meta o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
for (int i = 0; i < metas.size(); i++) {
Meta meta = metas.get(i);
Map<String, Tag> tags = meta.getTags();
if (tags != null) {
for (Tag t : tags.values()) {
t.setPos(i);
Tag holdTag = lastTagHolder.get(t.getName());
if (holdTag != null) {
t.setPrevious(holdTag.getPos());
holdTag.setNext(i);
}
lastTagHolder.put(t.getName(), t);
}
}
}
for (Meta meta : metas) {
this.indexStore.append(meta);
}
//写入到indexStore后,就清空metas.
this.metas.clear();
} finally {
writeLock.unlock();
}
}
@Override
public void close() throws IOException {
try {
writeLock.lock();
this.indexStore.close();
this.dataStore.close();
} finally {
writeLock.unlock();
}
}
@Override
public byte[] get(int indexPos) throws IOException {
Meta meta = this.indexStore.getIndex(indexPos);
if (meta == null) return null;
return getValue(meta);
}
@Override
public byte[] get(String key) throws IOException {
Meta meta = getIndex(key);
if (meta == null) {
return null;
}
return getValue(meta);
}
@Override
public byte[] get(String key, String tag) throws IOException {
Meta meta = getIndex(key, tag);
if (meta == null) {
return null;
}
return getValue(meta);
}
@Override
public Meta getIndex(int indexPos) throws IOException {
return this.indexStore.getIndex(indexPos);
}
@Override
public Meta getIndex(String key) throws IOException {
return this.indexStore.getIndex(key);
}
@Override
public Meta getIndex(String key, String tag) throws IOException {
return this.indexStore.getIndex(key, tag);
}
@Override
public Record getRecord(String key, String tag) throws IOException {
throw new UnsupportedOperationException();
}
private byte[] getValue(Meta meta) throws IOException {
return this.dataStore.get(meta.getOffset(), meta.getLength());
}
@Override
public long size() throws IOException {
return this.indexStore.size();
}
@Override
public boolean delete() throws IOException {
boolean dataDeleted = this.dataStore.delete();
boolean indexDeleted = this.indexStore.delete();
return dataDeleted && indexDeleted;
}
public boolean deleteLocal() throws IOException {
boolean dataDeleted = this.dataStore.deleteLocal();
boolean indexDeleted = this.indexStore.deleteLocal();
return dataDeleted && indexDeleted;
}
public boolean deleteRemote() throws IOException {
boolean dataDeleted = this.dataStore.deleteRemote();
boolean indexDeleted = this.indexStore.deleteRemote();
return dataDeleted && indexDeleted;
}
@Override
public byte[] getNext(String key, String tagName) throws IOException {
Meta meta = this.getIndex(key, tagName);
if (meta == null) {
return null;
}
Map<String, Tag> tags = meta.getTags();
if (tags == null) {
return null;
}
Tag tag = tags.get(tagName);
if (tag == null) {
return null;
}
return this.get(tag.getNext());
}
@Override
public byte[] getPrevious(String key, String tagName) throws IOException {
Meta meta = this.getIndex(key, tagName);
if (meta == null) {
return null;
}
Map<String, Tag> tags = meta.getTags();
if (tags == null) {
return null;
}
Tag tag = tags.get(tagName);
if (tag == null) {
return null;
}
return this.get(tag.getPrevious());
}
}