53.性能调优之使用 fastutil 优化数据格式

来源:互联网 发布:淘宝apass会员资格 编辑:程序博客网 时间:2024/06/04 23:30

本文为《Spark大型电商项目实战》 系列文章之一,主要介绍在实际项目中使用 fastutil 优化数据格式的方法。

fastutil 介绍

fastutil 是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的类库,提供了特殊类型的map、set、list和queue。

fastutil 能够提供更小的内存占用,更快的存取速度。我们使用fastutil提供的集合类,来替代自己平时使用的JDK的原生的Map、List、Set,好处在于:fastutil集合类,可以减小内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素的值的时候,提供更快的存取速度。

fastutil也提供了64位的array、set 和 list 以及高性能快速的,以及实用的IO类来处理二进制和文本类型的文件。fastutil的每一种集合类型都实现了对应的 Java 中的标准接口(比如fastutil的map实现了Java的Map接口),因此可以直接放入已有系统的任何代码中。

fastutil 还提供了一些JDK标准类库中没有的额外功能(比如双向迭代器)。
fastutil 除了对象和原始类型为元素的集合,fastutil也提供引用类型的支持,但是对引用类型是使用等于号(=)进行比较的,而不是equals()方法。fastutil尽量提供了在任何场景下都是速度最快的集合类库。

Spark中应用fastutil的场景

  1. 如果算子函数使用了外部变量,那么第一,你可以使用Broadcast广播变量优化;第二,可以使用Kryo序列化类库,提升序列化性能和效率;第三,如果外部变量是某种比较大的集合,那么可以考虑使用 fastutil 改写外部变量,首先从源头上就减少内存的占用,通过广播变量进一步减少内存占用,再通过Kryo序列化类库进一步减少内存占用。

  2. 在你的算子函数里,也就是task要执行的计算逻辑里面,如果有逻辑中出现要创建比较大的 Map、List 等集合,可能会占用较大的内存空间,而且可能涉及到消耗性能的遍历、存取等集合操作,那么此时可以考虑将这些集合类型使用 fastutil 类库重写,使用了 fastutil 集合类以后,就可以在一定程度上减少task创建出来的集合类型的内存占用,避免 executor 内存频繁占满,频繁唤起GC,导致性能下降。

fastutil调优说明

fastutil 其实没有你想象中的那么强大,也不会跟官网上说的效果那么一鸣惊人。之前所说的广播变量、Kryo序列化类库、fastutil,对于性能来说类似于一种调味品。分配资源、并行度、RDD架构与持久化,这三个是性能提升最明显的,而broadcast、kryo、fastutil 等类似于调料的作用,起到部分加强提升的功能。

比如说你的spark作业,经过之前一些调优以后,大概30分钟运行完,现在加上broadcast、kryo、fastutil,也许就是优化到29分钟运行完,或者更好一点,也许就是28分钟或25分钟。如果使用shuffle调优或许会降到15分钟;groupByKey用reduceByKey改写,执行本地聚合后也许就10分钟;如果跟公司申请更多的资源,比如资源更大的YARN队列,可能1分钟就可以运行完。

代码实现

第一步:在 pom.xml 中引用 fastutil 的包

<dependency>    <groupId>fastutil</groupId>    <artifactId>fastutil</artifactId>    <version>5.0.9</version></dependency>

(速度比较慢,可能是从国外的网去拉取jar包,可能要等待5分钟,甚至几十分钟不等)

然后修改代码,以“session随机抽取功能”中的List<Integer>为例,在

final Broadcast<Map<String, Map<String, List<Integer>>>> dateHourExtractMapBroadcast =                 sc.broadcast(dateHourExtractMap);

之前加上

Map<String, Map<String, IntList>> fastutilDateHourExtractMap =                 new HashMap<String, Map<String, IntList>>();        for(Map.Entry<String, Map<String, List<Integer>>> dateHourExtractEntry :                 dateHourExtractMap.entrySet()) {            String date = dateHourExtractEntry.getKey();            Map<String, List<Integer>> hourExtractMap = dateHourExtractEntry.getValue();            Map<String, IntList> fastutilHourExtractMap = new HashMap<String, IntList>();            for(Map.Entry<String, List<Integer>> hourExtractEntry : hourExtractMap.entrySet()) {                String hour = hourExtractEntry.getKey();                List<Integer> extractList = hourExtractEntry.getValue();                IntList fastutilExtractList = new IntArrayList();                for(int i = 0; i < extractList.size(); i++) {                    fastutilExtractList.add(extractList.get(i));                  }                fastutilHourExtractMap.put(hour, fastutilExtractList);            }            fastutilDateHourExtractMap.put(date, fastutilHourExtractMap);        }

然后将

final Broadcast<Map<String, Map<String, List<Integer>>>> dateHourExtractMapBroadcast =                 sc.broadcast(dateHourExtractMap);

改写为

final Broadcast<Map<String, Map<String, IntList>>> dateHourExtractMapBroadcast =                 sc.broadcast(fastutilDateHourExtractMap);

在之前改写的广播变量处将

final Broadcast<Map<String, Map<String, List<Integer>>>> dateHourExtractMapBroadcast =                 sc.broadcast(dateHourExtractMap);

改为

Map<String, Map<String, IntList>> dateHourExtractMap =                                 dateHourExtractMapBroadcast.value();

最后记得在“构建Spark上下文”处的Kryo中注册 IntList

        SparkConf conf = new SparkConf()                .setAppName(Constants.SPARK_APP_NAME_SESSION)                .setMaster("local")                .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")                .registerKryoClasses(new Class[]{                        CategorySortKey.class,                        IntList.class});     

至此,使用 fastutil 优化完成。

本文为《Spark大型电商项目实战》系列文章之一,
更多文章:Spark大型电商项目实战:http://blog.csdn.net/u012318074/article/category/6744423

0 0
原创粉丝点击