package com.alibaba.doris.dataserver.store.kyotocabinet; import java.io.File; import java.util.Iterator; import java.util.List; import kyotocabinet.Cursor; import kyotocabinet.DB; import com.alibaba.doris.common.data.CompareStatus; import com.alibaba.doris.common.data.Key; import com.alibaba.doris.common.data.Pair; import com.alibaba.doris.common.data.Value; import com.alibaba.doris.common.data.impl.PairImpl; import com.alibaba.doris.dataserver.store.BaseStorage; import com.alibaba.doris.dataserver.store.ClosableIterator; import com.alibaba.doris.dataserver.store.StorageType; import com.alibaba.doris.dataserver.store.exception.VersionConflictException; import com.alibaba.doris.dataserver.store.serialize.KeyValueSerializerFactory; /** * @author ajun Email:jack.yuj@alibaba-inc.com */ public class KyotocabinetDatabase extends BaseStorage { public KyotocabinetDatabase(String databaseName, KyotocabinetStorageConfig config) { this.databaseName = databaseName; this.configure = config; this.isOpen = false; database = new DB(); } public synchronized void open() { if (!isOpen) { String fileName = getDatabaseFileName(); // open the database if (!database.open(fileName, DB.OWRITER | DB.OCREATE)) { throw new KyotocabinetStorageException("open error: " + database.error()); } isOpen = true; } } protected String getDatabaseFileName() { return configure.getDatabasePath() + File.separatorChar + getDatabaseName() + DB_SUFIX_HASH; } public String getDatabaseName() { return this.databaseName; } public synchronized void close() { if (isOpen) { if (!database.close()) { throw new KyotocabinetStorageException("Close database " + databaseName + " failed!"); } isOpen = false; } } public boolean delete(Key key) { byte[] keyBytes = serializerFactory.encode(key).copyBytes(); return database.remove(keyBytes); } public boolean delete(Key key, Value value) { byte[] keyBytes = serializerFactory.encode(key).copyBytes(); int tryTimes = 0; do { byte[] oldValueBytes = database.get(keyBytes); if (null != oldValueBytes) { Value oldValue = serializerFactory.decodeValue(oldValueBytes); if (oldValue.compareVersion(value) == CompareStatus.AFTER) { throw new VersionConflictException("Key " + key.getKey() + " Namespace " + key.getNamespace() + " " + value.getTimestamp() + " is obsolete, it is no greater than the current version of " + oldValue.getTimestamp() + "."); } if (database.cas(keyBytes, oldValueBytes, null)) { return true; } tryTimes++; if (tryTimes >= 3) { break; } sleep(1); } else { break; } } while (true); return false; } public boolean delete(List<Integer> vnodeList) { return false; } public Value get(Key key) { byte[] keyBytes = serializerFactory.encode(key).copyBytes(); byte[] valueBytes = database.get(keyBytes); if (null != valueBytes) { return serializerFactory.decodeValue(valueBytes); } return null; } public void set(Key key, Value value) { set(key, value, false); } public void set(Key key, Value value, boolean isSetWithCompareVersion) { byte[] keyBytes = serializerFactory.encode(key).copyBytes(); byte[] valueBytes = serializerFactory.encode(value).copyBytes(); if (isSetWithCompareVersion) { int tryTimes = 0; do { byte[] oldValueBytes = database.get(keyBytes); if (null != oldValueBytes) { Value oldValue = serializerFactory.decodeValue(oldValueBytes); if (oldValue.compareVersion(value) == CompareStatus.AFTER) { throw new VersionConflictException( "Key " + key.getKey() + " Namespace " + key.getNamespace() + " " + value.getTimestamp() + " is obsolete, it is no greater than the current version of " + oldValue.getTimestamp() + "."); } if (database.cas(keyBytes, oldValueBytes, valueBytes)) { break; } tryTimes++; if (tryTimes >= 3) { break; } sleep(1); } else { if (!database.set(keyBytes, valueBytes)) { throw new KyotocabinetStorageException("Set value error: " + database.error()); } break; } } while (true); } else { if (!database.set(keyBytes, valueBytes)) { throw new KyotocabinetStorageException("Set value error: " + database.error()); } } } public String getName() { return databaseName; } public StorageType getType() { return null; } public Iterator<Pair> iterator() { return new KyotocabinetIterator(database); } public Iterator<Pair> iterator(List<Integer> vnodeList) { return new KyotocabinetIterator(database); } private static class KyotocabinetIterator implements ClosableIterator<Pair> { public KyotocabinetIterator(DB db) { this.cursor = db.cursor(); this.cursor.jump(); this.currentPair = getNextPair(); } public void close() { cursor.disable(); } public boolean hasNext() { return currentPair != null; } public Pair next() { Pair cur = currentPair; currentPair = getNextPair(); return cur; } public void remove() { cursor.remove(); } private Pair getNextPair() { byte[] keyBytes = cursor.get_key(false); if (null != keyBytes) { byte[] valueBytes = cursor.get_value(true); Key k = serializerFactory.decodeKey(keyBytes); Value v = serializerFactory.decodeValue(valueBytes); return new PairImpl(k, v); } return null; } private Pair currentPair; private Cursor cursor; } private void sleep(int sleepTime) { try { Thread.sleep(sleepTime); } catch (InterruptedException ignore) { // TODO ignore exception } } private volatile boolean isOpen; private String databaseName; private DB database; private KyotocabinetStorageConfig configure; private static final String DB_SUFIX_HASH = ".kch"; private static final KeyValueSerializerFactory serializerFactory = KeyValueSerializerFactory.getInstance(); }