storm 中小技巧
1、 TimeCacheMap过期缓存,采用桶实现,锁的粒度小,O(1)时间内完成锁操作,因此,大部分时间内都可以进行get和put操作。几乎所有的操作都是相对于桶数目线性的(O(numBuckets))。唯一的问题是缓存中可能存在过期的数据,也就是说真正的超时时间介于expirationSecs和expirationSecs * (1 + 1 / (numBuckets-1))之间。
public TimeCacheMap(int expirationSecs, int numBuckets, ExpiredCallback<K, V> callback) {
if(numBuckets<2) {
throw new IllegalArgumentException("numBuckets must be >= 2");
}
_buckets = new LinkedList<HashMap<K, V>>();
for(int i=0; i<numBuckets; i++) {
_buckets.add(new HashMap<K, V>());
}
_callback = callback;
final long expirationMillis = expirationSecs * 1000L;
final long sleepTime = expirationMillis / (numBuckets-1);
_cleaner = new Thread(new Runnable() {
@SuppressWarnings("unchecked")
public void run() {
try {
while(true) {
Map<K, V> dead = null;
Time.sleep(sleepTime);
synchronized(_lock) {
dead = _buckets.removeLast();
_buckets.addFirst(new HashMap<K, V>());
}
if(_callback!=null) {
for(Entry<K, V> entry: dead.entrySet()) {
_callback.expire(entry.getKey(), entry.getValue());
}
}
}
} catch (InterruptedException ex) {
}
}
});
_cleaner.setDaemon(true);
_cleaner.start();
}
2. 对元组的序列化采用kryo,并且使用了动态池的概念。这样避免了大量对象创建和销毁的开销,增加了效率
public class ThreadResourceManager<T> {
public static interface ResourceFactory<X> {
X makeResource();
}
ResourceFactory<T> _factory;
ConcurrentLinkedQueue<T> _resources = new ConcurrentLinkedQueue<T>();
public ThreadResourceManager(ResourceFactory<T> factory) {
_factory = factory;
}
public T acquire() {
T ret = _resources.poll();
if(ret==null) {
ret = _factory.makeResource();
}
return ret;
}
public void release(T resource) {
_resources.add(resource);
}
}
3. 对于supervisor进程和worker进程都在本地保存一个kv库,用于同步。kv实现为LocalState,一个基于map实现, 每次读写都需要直接操作磁盘的简单数据库。每次put数据时都要序列化数据,在序列化数据过程中,为了同步和原子性,采用了VersionedStore类,就是每次更新过程中不会去update现有的文件, 而是不断的产生递增version的文件, 故每一批更新都会产生一个新的文件。
VersionedStore类中采用两种文件实现原子性和同步性:
VersionFile, _root + version, 真正的数据存储文件
TokenFile, _root + version + “.version”, 标志位文件, 标志version文件是否完成写操作, 以避免读到正在更新的文件
private void persist(Map<Object, Object> val, boolean cleanup) throws IOException {
byte[] toWrite = Utils.serialize(val);//序列化要写入的数据
String newPath = _vs.createVersion();//创建新的versionfile的path
FileUtils.writeByteArrayToFile(new File(newPath), toWrite);//数据写入versionfile
_vs.succeedVersion(newPath);//调用succeedVersion, 创建tokenfile以标志versionfile的写入完成
if(cleanup) _vs.cleanup(4);//清除旧版本, 只保留4个版本
}
4. 待续