Amazon S3s密钥背后的数据结构(过滤数据结构)

时间:2010-03-12 06:40:59

标签: java data-structures amazon-s3 filtering hashmap

我想实现类似于Amazon S3的查找功能的数据结构。对于上下文,Amazon S3将所有文件存储在平面命名空间中,但允许您按名称中的公共前缀查找文件组,从而复制目录树的功能而不会产生复杂性。

问题是,查找和过滤操作都是O(1)(或者足够接近甚至在非常大的存储桶上 - S3的磁盘等价 - 两个操作也可能是O(1))。

简而言之,我正在寻找一种功能类似于哈希映射的数据结构,并且具有高效(至少不是O(n))过滤的额外好处。我能想到的最好的方法是扩展HashMap,使它还包含一个(已排序的)内容列表,并对匹配前缀的范围进行二进制搜索,然后返回该集合。这对我来说似乎很慢,但我想不出有任何其他方法可以做到。

有没有人知道亚马逊是如何做到的,或者更好的方式来实现这种数据结构?

2 个答案:

答案 0 :(得分:4)

只是为了验证我的说法,即常规TreeMap应该足以容纳多达1,000,000个条目的任何存储桶,这是一个非常简单的测试用例,它提供了一些数字(注意:这不是一个微基准测试,它只是为了得到一个感受到这个问题的严重性。)

我使用随机生成的UUID来模仿键(如果你用斜线替换破折号,你甚至会得到一种目录结构)。之后,我将它们放入常规java.util.TreeMap中,最后使用map.subMap(fromKey, toKey)查询它们。

public static void main(String[] args) {

    TreeMap<String, Object> map = new TreeMap<String, Object>();

    int count = 1000000;
    ArrayList<String> uuids;

    {
        System.out.print("generating ... ");
        long start = System.currentTimeMillis();
        uuids = new ArrayList<String>(count);
        for (int i = 0; i < count; i++) {
            uuids.add(UUID.randomUUID().toString());
        }
        System.out.println((System.currentTimeMillis() - start) + "ms");
    }

    {
        System.out.print("inserting .... ");
        long start = System.currentTimeMillis();

        Object o = new Object();
        for (int i = 0; i < count; i++) {
            map.put(uuids.get(i), o);
        }

        System.out.println((System.currentTimeMillis() - start) + "ms");
    }

    {
        System.out.print("querying ..... ");

        String from = "be400000-0000-0000-0000-000000000000";
        String to =   "be4fffff-ffff-ffff-ffff-ffffffffffff";

        long start = System.currentTimeMillis();

        long matches = 0;

        for (int i = 0; i < count; i++) {
            Map<String, Object> result = map.subMap(from, to);
            matches += result.size();
        }

        System.out.println((System.currentTimeMillis() - start) + "ms (" + matches/count
                + " matches)");

    }
}

以下是我机器的一些示例输出(1,000,000个密钥,1,000,000个范围查询):

generating ... 6562ms
inserting .... 2933ms
querying ..... 5344ms (229 matches)

插入1个密钥平均花费0.003毫秒(当然更接近结束),而查询229个匹配的子范围每个查询需要0.005毫秒。这是一些非常理智的表现,不是吗?

将数量增加到10,000,000个密钥和查询后,数字如下:

generating ...  59562ms
inserting ....  47099ms
querying ..... 444119ms (2430 matches)

插入1个密钥平均需要0.005毫秒,而查询2430匹配的子范围需要每个查询0.044毫秒。即使查询速度慢了10倍(最后,它会迭代所有匹配,总是O(n)),性能仍然不是太差。

由于S3是一项云服务,我认为它至少受到网络的限制。因此,迫切需要一种极其奇特的数据结构来获得所需的性能。不过,我的测试用例中缺少一些功能,最明显的是并发性和持久性。尽管如此,我认为我已经证明了常规树结构足以满足这个用例。如果你想做一些奇特的事情,可以尝试使用子树读写锁定,也可以替换.subMap(fromKey,toKey);

答案 1 :(得分:1)

只是附加sfussinigger的答案;使用ConcurrentSkipListMap很容易并发,并且它具有类似于TreeMap的属性。它不是太“花哨”的数据结构(无论如何,它已经为你实现)。它肯定比子树读写锁定更容易。