Storm常见模式5——TimeCacheMap

转载 2015年11月18日 19:55:58

Storm中使用一种叫做TimeCacheMap的数据结构,用于在内存中保存近期活跃的对象,它的实现非常地高效,而且可以自动删除过期不再活跃的对象。

TimeCacheMap使用多个桶buckets来缩小锁的粒度,以此换取高并发读写性能。下面我们来看看TimeCacheMap内部是如何实现的。

1. 实现原理

桶链表:链表中每个元素是一个HashMap,用于保存key,value格式的数据。

    private LinkedList<HashMap<K, V>> _buckets;

锁对象:用于对TimeCacheMap进行get/put等操作时上锁保证原子性。

    private final Object _lock = new Object();

后台清理线程:负责超时后清理数据。

    private Thread _cleaner;

超时回调接口:用于超时后进行函数回调,做一些其他处理。

    public static interface ExpiredCallback<K, V> {
        public void expire(K key, V val);
    }
    private ExpiredCallback _callback;

有了以上数据结构,下面来看看构造函数的具体实现:

1、 首先,初始化指定个数的bucket,以链式链表形式存储,每个bucket中放入空的HashMap;

2、 然后,设置清理线程,处理流程为:

  a)   休眠expirationMillis / (numBuckets-1)毫秒时间(即:expirationSecs / (numBuckets-1)秒);

  b)   对_lock对象上锁,然后从buckets链表中移除最后一个元素;

  c)   向buckets链表头部新加入一个空的HashMap桶,解除_lock对象锁;

  d)   如果设置了callback函数,则进行回调。

复制代码
    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() {
            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:桶的个数;callback:超时回调函数。

为了方便使用,还提供了以下三种形式的构造函数,使用时可以根据需要选择:

复制代码
    //this default ensures things expire at most 50% past the expiration time
    private static final int DEFAULT_NUM_BUCKETS = 3;
    public TimeCacheMap(int expirationSecs, ExpiredCallback<K, V> callback) {
        this(expirationSecs, DEFAULT_NUM_BUCKETS, callback);
    }
    public TimeCacheMap(int expirationSecs, int numBuckets) {
        this(expirationSecs, numBuckets, null);
    }
    public TimeCacheMap(int expirationSecs) {
        this(expirationSecs, DEFAULT_NUM_BUCKETS);
    }
复制代码

2. 性能分析

get操作:遍历各个bucket,如果存在指定的key则返回,时间复杂度为O(numBuckets)

复制代码
    public V get(K key) {
        synchronized(_lock) {
            for(HashMap<K, V> bucket: _buckets) {
                if(bucket.containsKey(key)) {
                    return bucket.get(key);
                }
            }
            return null;
        }
    }
复制代码

put操作:将key,value放到_buckets的第一个桶中,然后遍历其他numBuckets-1个桶,从HashMap中移除其中键为key的记录,时间复杂度为O(numBuckets)

复制代码
    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);
            }
        }
    }
复制代码

remove操作:遍历各个bucket,如果存在以key为键的记录,直接删除,时间复杂度为O(numBuckets)

复制代码
    public Object remove(K key) {
        synchronized(_lock) {
            for(HashMap<K, V> bucket: _buckets) {
                if(bucket.containsKey(key)) {
                    return bucket.remove(key);
                }
            }
            return null;
        }
    }
复制代码

containsKey操作:遍历各个bucket,如果存在指定的key则返回true,否则返回false,时间复杂度为O(numBuckets)

复制代码
    public boolean containsKey(K key) {
        synchronized(_lock) {
            for(HashMap<K, V> bucket: _buckets) {
                if(bucket.containsKey(key)) {
                    return true;
                }
            }
            return false;
        }
    }
复制代码

size操作:遍历各个bucket,累加各个bucket的HashMap的大小,时间复杂度为O (numBuckets)

复制代码
    public int size() {
        synchronized(_lock) {
            int size = 0;
            for(HashMap<K, V> bucket: _buckets) {
                size+=bucket.size();
            }
            return size;
        }
    }
复制代码

3. 超时时间

经过上面对put操作和_cleaner线程的分析,我们已经知道:

  a) put操作将数据放到_buckets的第一个桶中,然后遍历其他numBuckets-1个桶,从HashMap中移除其中键为key的记录;

  b) _cleaner线程每隔expirationSecs / (numBuckets-1)秒会把_buckets中最后一个桶中的数据从TimeCacheMap中移除掉。

因此,假设_cleaner线程刚刚清理数据,put函数调用发生将key放入桶中,那么一条数据的超时时间为:

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

然而,假设put函数调用刚刚执行结束,_cleaner线程就开始清理数据,那么一条数据的超时时间为:

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

4. 总结

1、 TimeCacheMap的高效之处在于锁的粒度小,O(1)时间内完成锁操作,因此,大部分时间内都可以进行get和put操作。

2、 get,put,remove,containsKey和size操作都可以在O(numBuckets)时间内完成,其中numBuckets是桶的个数,默认为3。

3、 未更新数据的超时时间在expirationSecs和expirationSecs * (1 + 1 / (numBuckets-1))之间。

转载: http://www.cnblogs.com/panfeng412/archive/2012/06/26/storm-common-patterns-of-timecachemap.html

相关文章推荐

Storm常见模式——流聚合及 timecachemap的使用

转自:http://www.cnblogs.com/panfeng412/archive/2012/06/04/storm-common-patterns-of-stream-join.html ...

Storm常见模式——TimeCacheMap

Storm中使用一种叫做TimeCacheMap的数据结构,用于在内存中保存近期活跃的对象,它的实现非常地高效,而且可以自动删除过期不再活跃的对象。 TimeCacheMap使用多个桶buckets...
  • z_l_l_m
  • z_l_l_m
  • 2012年12月18日 17:42
  • 497

Storm常见模式——求TOP N

Storm的另一种常见模式是对流式数据进行所谓“streaming top N”的计算,它的特点是持续的在内存中按照某个统计指标(如出现次数)计算TOP N,然后每隔一定时间间隔输出实时计算后的TOP...
  • z_l_l_m
  • z_l_l_m
  • 2012年12月18日 17:45
  • 757

Storm常见模式——分布式RPC

本文翻译自:https://github.com/nathanmarz/storm/wiki/Distributed-RPC,作为学习Storm DRPC的资料,转载必须以超链接形式标明文章原始出处及...
  • z_l_l_m
  • z_l_l_m
  • 2012年12月18日 17:40
  • 592

Storm常见模式——批处理

Storm对流数据进行实时处理时,一种常见场景是批量一起处理一定数量的tuple元组,而不是每接收一个tuple就立刻处理一个tuple,这样可能是性能的考虑,或者是具体业务的需要。 例如,批量查询...
  • z_l_l_m
  • z_l_l_m
  • 2012年12月18日 17:44
  • 899

Storm常见模式——流聚合

流聚合(stream join)是指将具有共同元组(tuple)字段的数据流(两个或者多个)聚合形成一个新的数据流的过程。 从定义上看,流聚合和SQL中表的聚合(table join)很像,但是二者...

Twitter Storm源代码分析之TimeCacheMap-过期清除

作者: xumingming | 可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明 网址: http://xumingming.sinaapp.com/395/twit...

storm实战--TimeCacheMap的使用

经过实践发现,原文对TimeCacheMap的使用是错误的(#线下方是原文章),实现TimeCacheMap的序列化后,TimeCacheMap的自动删除的功能将失效。原因应该是在序列化过程中Time...
  • smn3255
  • smn3255
  • 2012年04月13日 20:49
  • 2873

storm源码分析--TimeCacheMap

TimeCacheMap是storm里面的一个类,storm用它来保存那些最近活跃的duixiang

Twitter Storm源代码分析之TimeCacheMap

Twitter Storm源代码分析之TimeCacheMap 发表于 2011 年 12 月 27 日 由 xumingming 作者: xumingming | 可以转载, 但...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Storm常见模式5——TimeCacheMap
举报原因:
原因补充:

(最多只允许输入30个字)