storm源码分析--TimeCacheMap

来源:互联网 发布:opencv读取caffe 编辑:程序博客网 时间:2024/05/18 02:10

TimeCacheMap是storm里面的一个类,storm用它来保存那些最近活跃的对象,并且可以自动删除那些已经过期的对象。这个类设计的很巧妙,我们来看一下:

TimeCacheMap里面的数据是保存在内部变量_bucket里面的:

private LinkedList<HashMap<K, V>> _buckets;
在这点上跟ConcurrentHashMap有点类似,ConcurrentHashMap是利用多个bucket来缩小锁的粒度,从而实现高并发的读写。而TimeCacheMap则是利用多个bucket来使得数据清理线程占用锁的时间最小。

首先来看看TimeCacheMap的构造函数,它的构造函数首先是生成numBuckets个空的HashMap:

_buckets = new LinkedList<HashMap<K,V>>;for(int i = 0; i < numBuckets; i ++){    _buckets.add(new HashMap<K, V>)();}
然后就是最关键的线程清理部分,TimeCacheMap使用一个单独的线程来清理那些过期的数据:

final long expirationMillis = expirationSecs * 1000L;final long sleepTime = expirationMillis/(numBuckets-1);_cleaner = new Thread(new Runnable() {    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();
这个线程每隔expirationSecs/(numBuckets - 1)秒钟的时间去把最后一个bucket里面的数据全部都删掉--这些被删掉的数据其实就是过期数据。这里值得注意的是:正式因为这种分成多个桶的机制,清理线程对于_lock的占用时间极短,只要将最后一个bucket从——buckets解下,并且向头添加一个新的bucket就好了
synchronized(_lock) {    dead = _buckets.removeLast();    _buckets.addFirst(new HashMap<K, V>());}
如果不是这种机制,那么我能想到最傻的办法可能就是给条数据一个过期时间字段,然后清理线程就要便利每条数据来检查数据是否过期了,这显然要hold住这个锁很长时间。

同时对于每条过期数据TimeCacheMap会执行callback函数

if(_callback!=null) {     for(Entry<K, V> entry: dead.entrySet()) {          _callback.expire(entry.getKey(), entry.getValue());     }}
大致机制就是这样,那么我们现在回头看看前面的那个问题:为什么这个清理线程是每隔expirationSecs / (numbuckets - 1)秒的时间来检查,这样对吗?TimeCacheMap的内部有多个桶,当你向这个TimeCacheMap里面添加数据的时候,数据总是添加到第一个桶里面的。

public void put(K key, V value) {    synchronized(_lock) {        Iterator<HashMap<K, V>> it = _buckets.iterator();        HashMap<K, V> bucket = it.next();        bucket.put(key, value);        while(it.hasNext()) {            bucket = it.next();            bucket.remove(key);        }    }}
我们看个例子就明白了,假设numBuckets = 3,expirationSecs = 2.我们先往里面填一条数据{1: 1},这条数据被加到第一个桶里面去,现在TimeCacheMap的状态是
[{1:1}, {}, {}]
过了一秒钟后(expirationSecs / (numBuckets - 1) = 2/(3-1) = 1).清理线程干掉最后一个HashMap,并且在头上添加一个空的HashMap,现在的TimeCacheMap的状态是:

[{}, {1:1}, {}]
再过了一秒钟,同上,TimeCacheMap的状态变成:

[{}, {}, {1:1}]
再过一秒钟,现在{1:1}是最后一个TimeCacheMao了,就被干掉了。所以从{1:1}被加入到这个TimeCacheMap到被干掉一共用了3秒,其实这3秒就等于

3 = expirationSecs *(1+1/(numBuckets - 1))


storm当中0.8版本之前的TimeCacheMap这个map中的那个算法是不是有问题呢?里面记录的消息的过期时间是expirationSecs到expirationSecs * (1 + 1 / (numBuckets-1))之间,如果我设置的expirationSecs比较大,那岂不是这个时间差会很大,比如expirationSecs是100,numBuckets是3,那么这个时间就是100s到150s,这个不能容忍吧.还是说这个map只是为了存储活跃的数据,对过期数据的删除其实并没有严格的要求,个人认为是这样的,所以作者在0.8之后不再使用TimeCacheMap


有时间可以了解一下guava








0 0
原创粉丝点击