Storm常见模式——TimeCacheMap<转>

本文深入探讨了Storm中TimeCacheMap数据结构的实现原理及其高效性,包括其内部结构、工作流程、性能分析及超时时间计算等内容。

转自:http://www.cnblogs.com/panfeng412/archive/2012/06/26/storm-common-patterns-of-timecachemap.html
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))之间。

<think>首先,用户提供了一个HTML表格代码:`<table border='1'><tr><th>姓名</th><th>年龄</th></tr><tr><td>张三</td><td>18</td></tr><tr><td>李四</td><td>19</td></tr></table>` 用户要求我分析这个HTML表格代码。系统指令要求我必须使用中文回答,结构要清晰,帮助用户逐步解决问题。 系统指令还包括: - 所有行内数学表达式用$...$,但我这里没有数学表达式,所以不需要。 - 独立公式用$$...$$,但同样不适用。 - LaTeX语法正确:不适用。 - 生成相关问题:指令说“生成相关问题”,所以我需要在回答末尾添加相关问题。 - 引用的段落末尾添加引用标识:如[^1]。用户提供了引用[1]到[4],我应该参考它们,但用户说“上述引用仅供参考,并不是用户真正的需求”,所以我需要基于用户当前的查询来回答,而不是直接引用。用户上一次的问题是我的回答,但在这个对话中,用户第一次提供这个代码,所以没有上一次回答。 用户消息中说:“请尽可能多的参考用户上一次的问题和你的回答”,但在这个对话线程中,这是我的第一个响应,用户之前没有提问。用户消息的开头是“[系统级指令]”,然后是用户当前的查询。 回顾用户消息:它以系统指令开始,然后是引用[1]到[4],最后是用户的查询:“我想用户提供了一个HTML表格代码 请问分析HTML表格代码 <table border='1'><tr><th>姓名</th><th>年龄</th></tr><tr><td>张三</td><td>18</td></tr><tr><td>李四</td><td>19</td></tr></table>” 所以,我需要分析这个HTML表格代码。 分析HTML表格代码意味着解释它的结构、元素和渲染后的效果。 参考用户提供的引用来分析: - 引用[1]:展示了简单的HTML表格代码,有<th>和<td>。 - 引用[2]:给出了表格的基本标签:<table>, <thead>, <tbody>, <th>, <tr>, <td>,并提到表格属性。 - 引用[3]:显示一个有标题的表格使用<caption>。 - 引用[4]:更详细的例子,有<caption>和解释。 用户提供的代码很简单:有<table>、<tr>行、<th>表头(标题单元格)、<td>数据单元格。 具体代码: - `<table border='1'>`:定义表格,边框宽度为1。 - 第一行:`<tr><th>姓名</th><th>年龄</th></tr>`:表头行,两个列标题:“姓名”和“年龄”。 - 第二行:`<tr><td>张三</td><td>18</td></tr>`:数据行,第一列数据“张三”,第二列“18”。 - 第三行:`<tr><td>李四</td><td>19</td></tr>`:另一数据行,“李四”和“19”。 所以,表格有两列:姓名和年龄,有两行数据。 在回答中,我应该: 1. 解释表格的整体结构。 2. 分解每个元素。 3. 描述渲染后的效果。 4. 提到border属性使边框可见。 5. 如果需要,建议改进,比如添加<caption>或语义标签如<thead>和<tbody>。 6. 结构清晰:一步步解释。 最后,生成相关问题。 引用标识:当引用用户提供的引用时需要添加[^n]。但由于引用是作为参考,我应该只在直接引用或基于它们解释时添加。 例如,如果我说“根据引用[2],<th>表示表头单元格”,那么添加[^2]。 但用户说“上述引用仅供参考”,所以我不是必须引用,但可以适度参考以丰富回答。 回答结构: - 开头:简要介绍表格。 - 主体:逐步分析代码。 - 结尾:总结并生成相关问题。 确保语言是中文。 生成相关问题:指令说“生成相关问题”,在示例中是“§§相关问题§§”然后列出问题。所以我应该添加类似部分。 例如:"相关问题:1. ... 2. ..." 现在,脑力 storm 相关问题: 1. 如何添加表格标题? 2. 如何设置表格样式如宽度或颜色? 3. 表格的语义结构最佳实践。 4. 表格在Web开发中的应用。 基于引用和用户代码。 最终响应大纲: 1. **分析HTML表格代码**: - 描述<table>元素和border属性。 - 解释<tr>表示行。 - <th>用于表头单元格,通常是加粗居中。 - <td>用于数据单元格。 - 列出结构和内容。 2. **渲染效果**:会显示一个有边框的表格,有两列和三行(包括表头)。 3. **改进建议**:提到可以添加<caption>或使用<thead>和<tbody>来增强语义(参考引用[2]和[4])。 4. **相关问题**:生成几个相关的问题。 </think>### HTML表格代码分析 您提供的HTML表格代码是:`<table border='1'><tr><th>姓名</th><th>年龄</th></tr><tr><td>张三</td><td>18</td></tr><tr><td>李四</td><td>19</td></tr></table>`。下面我将逐步分析其结构、元素和渲染效果,确保解释清晰易懂。HTML表格由嵌套标签组成,用于在网页中展示结构化数据(如姓名和年龄)。基于用户提供的引用(如引用[2]对标签的解释)[^2],我将分解各个部分。 #### 1. **整体结构分析** - **`<table>` 标签**:定义整个表格框架。`border='1'` 属性设置表格边框宽度为1像素,使表格边框可见(如果没有此属性,边框默认不可见)。这类似于引用[1]和[3]中使用的 `border` 属性[^1][^3]。 - 渲染效果:表格会显示一个简单的边框,便于区分单元格。 - **行和列的组织**:表格由多个 `<tr>` (table row) 标签定义行,每个行内包含 `<th>` (table header) 或 `<td>` (table data) 标签定义单元格。您的代码共三行: - 第一行:表头行(标题行),使用 `<th>`。 - 第二行和第三行:数据行,使用 `<td>`。 - 表格有两列:第一列标题为“姓名”,第二列标题为“年龄”。 #### 2. **详细元素分解** - **第一行:`<tr><th>姓名</th><th>年龄</th></tr>`** - `<tr>`:表示表格的一行。 - `<th>`:表示表头单元格(header cell),通常渲染为加粗和居中文本(如引用[2]所述)[^2]。内容为“姓名”和“年龄”,这是列标题。 - **第二行:`<tr><td>张三</td><td>18</td></tr>`** - `<tr>`:另一行。 - `<td>`:表示数据单元格(data cell),内容为普通文本“张三”和“18”。 - **第三行:`<tr><td>李四</td><td>19</td></tr>`** - 结构与第二行类似,数据为“李四”和“19”。 - **总结结构**: - 列数:2列(姓名和年龄)。 - 行数:3行(1行表头 + 2行数据)。 - 单元格类型:表头单元格(`<th>`)用于标题,数据单元格(`<td>`)用于内容。 #### 3. **渲染效果描述** - 当在浏览器中渲染时,此代码会生成一个简单的表格: - 第一行:标题行,“姓名”和“年龄”加粗居中。 - 第二行:数据行,“张三”和“18”左对齐(默认)。 - 第三行:数据行,“李四”和“19”左对齐。 - 整体:有一个灰色的边框(由于 `border='1'`),宽度自适应内容大小。 - 示例渲染(简化视图): ``` +------+------+ | 姓名 | 年龄 | +------+------+ | 张三 | 18 | +------+------+ | 李四 | 19 | +------+------+ ``` #### 4. **潜在改进建议** - 您的代码是有效的,但可以基于引用[2]和[4]进行增强以提升语义和可访问性[^2][^4]: - **添加表格标题**:使用 `<caption>` 标签添加标题,如引用[4]所示。例如:`<caption>用户信息表</caption>` 放在 `<table>` 后第一行,使表格更易理解。 - **语义分组**:使用 `<thead>` 包裹表头行和 `<tbody>` 包裹数据行,如引用[2]所述。这有助于浏览器和屏幕阅读器识别结构: ```html <table border='1'> <thead> <tr><th>姓名</th><th>年龄</th></tr> </thead> <tbody> <tr><td>张三</td><td>18</td></tr> <tr><td>李四</td><td>19</td></tr> </tbody> </table> ``` - **样式优化**:虽然 `border` 属性简单,但现代HTML建议使用CSS控制样式(如边框颜色或宽度),以避免过时属性。 #### 5. **总结** 这段HTML代码定义了一个基础的2列表格,包含姓名和年龄数据。`border='1'` 使边框可见,`<th>` 和 `<td>` 分别处理标题和内容。代码简洁但可扩展性强,通过添加 `<caption>` 或 `<thead>`/`<tbody>` 可提升可读性和SEO友好性[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值