高效的数据结构,可存储数百万条记录

时间:2014-07-31 20:00:31

标签: java hadoop memory-management bigdata

我有一个包含数百万条记录的输入文件,每条记录又包含数千列,其中每列都用分隔符分隔。

记录和列数可能因文件而异。

我要求我必须解析这些记录并将它们存储在java对象中,以便可以进一步传递给Drools Framework进行列级验证。

这就是我的输入数据和模式文件的样子。

输入文件:

John|Doe|35|10 Floyd St|132|Los Angeles|CA|USA ... and so on 
...
...
Millions records like this

架构文件:

firstName|String|false|20|NA
lastName|String|false|20|NA
age|Integer|false|3|NA
addressLine1|String|false|20|NA
addressLine2|String|false|20|NA
city|String|false|5|NA
state|String|false|10|NA
country|String|false|10|NA

我尝试在地图的帮助下实现此解决方案,并创建了一个包含此地图的Java类。

class GenericRecord {
   Map<String,FieldSpecification> properties; //used HashMap as an implementation
}

class FieldSpecification {
    public String fieldName;
    public String dataType;
    public int length;
    public String value;
    public String format;
}

对于输入文件中的覆盖行,我创建了一个Record对象,并使用map来存储其列的值。除此之外,我还在FieldSpecification对象中存储有关列的元数据,如dataType,length,format等。

对于我的输入文件中的几千行,它工作正常但是一旦行数开始增加,它就会因为内存问题(正如预期的那样)开始消失。因为它正在创建数百万个具有数千个键的对象映射。

我知道这不是解决这类问题的有效解决方案。

所以我担心基于内存的解决方案在我的场景中是否有效,或者我不得不选择基于磁盘的解决方案,如嵌入式数据库或基于磁盘的地图。

请告知我是否可以使用任何其他开源地图实施。

注意:对于文件分析和数据验证我使用的是hadoop,它运行在40个节点的集群上。

以下是我的映射器的流程和实现:

将值作为完整行接收,稍后将此行传递给Java框架,该框架将其转换为相应的GenericObject(如上所述),然后将此对象传递给drools框架以进行进一步验证。

Mapper实施:

public void map(LongWritable key , Text value , Context context) throws IOException, InterruptedException {

        //Convert the text value to string i.e line by line comes here
        String record = value.toString();





        // Develop a drools service that will take record as an input 
        // and will validate it on the basis of XL sheet provided
        workingMemory = knowledgeBase.newStatefulKnowledgeSession();
        DroolsObject recordObject = DroolsServiceImpl.validateByRecord(record, fileMetaData, workingMemory);



        //Check to validate if the processed record
        if(recordObject.isValid) {
            context.getCounter(AppCounter.VALID_RECORD).increment(1);
            mapperOutputKey.set("A");
            mapperOutputValue.set(recordObject.toString());
            context.write(mapperOutputKey,mapperOutputValue);
        }

        else {
            context.getCounter(AppCounter.INVALID_RECORD).increment(1);
            mapperOutputKey.set("R");
            mapperOutputValue.set(recordObject.toStringWithErrors());
            context.write(mapperOutputKey,mapperOutputValue);
        }
}

2 个答案:

答案 0 :(得分:1)

由于您必须将文件中的每个字节数据保存在内存中(除了可能的分隔符),首先要查看文件的大小并将其与内存大小进行比较。如果你的文件比内存大,那就抓住将它保存在内存中的全部想法。

如果内存大于文件,你有机会,虽然你需要仔细检查这个文件将来如何增长,程序运行的平台等等。

所以确定它适合,您可以更有效地使用数据结构。一种节省内存的简单方法是废弃地图并将每条记录保存为字符串(在文件中编码)。字符串数组应该具有最小的开销,但是您希望确保在填充原始数组时不会不断调整原始数组的大小。

当数据结构变大时保持数据结构的简单性可以为开销节省大量内存。

此外,如果数据很容易适应内存,您可能需要对JVM进行一些调整,以便为其分配足够的内存(更改堆大小using -Xmx)以使JVM足够大。我希望您在64位平台上使用64位JVM。

答案 1 :(得分:0)

我建议将数据保存在一个(byte[][])表中,并通过它们的编号引用行。然后,您可以使用按需读取相应字段的游标:

class FieldSpecification {
    private final int row;
    private final byte[][] mem;

    public String fieldName();
    public String dataType();
    public int length();
    public String value();
    public String format();
}

垃圾收集器应该很容易处理这些对象。你只需要关心他们的生命周期。

当字节数组不适合你的记忆时,那么你无论如何都要搞砸了。

然后,您可以通过将名称映射到行号来实现地图。