四两拨千斤:借助Spark GraphX将QQ千亿关系链计算提速20倍

时间 2016-07-22 16:57:00 炼数成金 相似文章 (5)
主题 Graphx Spark

腾讯QQ有着国内最大的关系链,而共同好友数,属于社交网络分析的基本指标之一,是其它复杂指标的基础。借助Spark GraphX,我们用寥寥100行核心代码,在高配置的TDW-Spark集群上,只花了2个半小时,便完成了原来需要2天的全量共同好友计算。这标志着QQ千亿级别的关系链计算进入了小时级别时代,并具备复杂图模型的快速计算能力。

问题描述

共同好友数可以用于刻画用户与用户间的关系紧密程度,包括 陌生人/熟人分析,好友亲密度,好友推荐,社团划分等各个方面,是社交网络分析的最基础指标。其计算逻辑非常简单明了。为了简化模型和降低计算量,这里加了几个约束:

只有好友之间才进行计算

好友关系是有向的

不关注具体的好友

显而易见,用户5和6的共同好友数为4。这个计算貌似非常简单,但是当图的规模扩展到腾讯的级别:用户数(点)为十亿级别,关系数(边)为千亿级别时,那这个问题就一点都不简单了。

大致估算一下,假设每个节点平均的好友数是100,每个点id为Long型,占用8个字节,如果用普通Join计算的话,那么中间的数据量大概是1 billion* 800*800B=640TB,需要通过网络传输640TB的数据,这个量非常的恐怖了,想在一个SQL中完成,几乎是不可能的。原来旧的计算方式,只能通过分而治之的方法实现。通过把关系链表,按号段拆分60份,分别连接用户好友全量表,分成多个SQL任务运行。这样做每个SQL任务都需要载入一次全量关系链,磁盘 I/O 时间严重拖慢计算进度,整个过程需要耗费超过两天的计算时间。

其实共同好友数是典型的图问题之一,因此很自然的,我们想到了引入新的图计算框架和模型,来优化计算过程,让这个过程更加高效和科学。

框架选择

目前的分布式图计算框架,并没有太多好的选择,在过去一年半中,业界的分布式图计算框架,进展基本停滞。经过反复选择,我们还是选择了GraphX,主要原因有如下3个:

进展

虽然GraphX本身没什么进展,但是Spark本身的发展很快,从1.4到1.6版本,Spark Core在性能和稳定性上有了不少的提升。GraphX某种程度上,多多少少还是得到了好处

语义

GraphX的语义和运算符相对丰富,可以进行比较好的图算法描述,适合变化多样的图需求

门槛

GraphX的最大消耗是内存,某种程度上,这是个比较低门槛的投入,可以在预期内得到解决

基于这样的考虑,我们选择了GraphX作为共同好友算法的底层框架,并从软件和硬件两方面入手,进行了一系列的优化。

模型简化

基于GraphX的图模型思想,我们进行数好友模型的简化。整个过程分为两个阶段:

Phrase1——找邻居

这个阶段,其实就是一放一收,和Map-Reduce模型有异曲同工之妙,分3步

1. 每个顶点,将自己的id,发送给自己所有的邻居

2. 每个顶点,将收到的所有邻居id,合并为一个List

3. 对新List进行排序,并和原来的图进行关联,附到顶点之上

这个阶段,使用GraphX的aggregateMessages,定义好sendMsg和mergeMsg的方法,就可轻松实现。

Phrase2——数好友

这个阶段,只要一步,但是非常的关键,而且计算量也非常大,需要充分的利用了Triplet的特性

1. 遍历所有的Triplet,对2个好友的有序好友List进行扫描匹配,数出共同好友数,并将其更新到edge之上

这个阶段,需要充分利用GraphX的Triplet特性。需要为了实现Triplet功能,GraphX在设计上消耗了很多的内存,无论我们是否使用,它都在那里,静静的占用着内存,所以我们要充分利用好它,将数好友的过程,简化为一次Triplet的遍历。

如果没有这个特性,为了数好友之间的共同好友,就要把一个好友的所有一度好友,发送到它所有的邻居之上,这样的消息广播量,是非常巨大的,会形成消息风暴,相当于计算二跳邻居。根据我们在腾讯数据量上的测试,这个在现有的硬件下,是无法实现的。

这2个阶段经过最终优化之后,代码非常的简洁,只要聊聊的数十行代码,便完成了20亿级别的点,千亿级别的边的共同好友计算,可谓于无声处听惊雷。将众多的技术难点,都通过GraphX的优化,化解消弭于无形之中。由于产品的需求,这个计算需要是精确数,而不能是近似值,在数好友的过程中,很多的优化方法,不能被用上,否则的话,可以进一步的提升速度。

硬件选择

在分布式系统中,往往会倾向于用大量的小低配机器,来完成巨大的计算任务。其思想是即便再复杂的计算,只要将大数据,分解为足够小的数据片,总能在足够多的机器上,通过性能的降低和时间的拖延,来完成计算任务。但是很遗憾,在图计算这样的场景下,尤其是GraphX的设计框架,这个是行不通的。

要发挥GraphX的最佳性能,最少要有128G以上的内存

主要原因有两个是:

节点复制——越小越浪费

GraphX使用了点切割的方式,这是一种用空间换时间的方法,通过将浪费一定的内存,将点和它的邻居放到一起,减少Executor之间的通信。

如果用小内存的Executor来运行图算法,假设1个节点,需要10个Executor才能放下它的邻居,那么它就需要被复制10份,才能进行计算。如果用大内存的Executor,1个Executor就能放下它的所有邻居,理论上它就只需要被复制一次,大大减少空间占用。

节点膨胀——越小越慢

图计算中,常常会进行消息的扩散和收集,并把最终的结果,汇总到单个节点之上。

以共同好友数模型为例,第一步需要将节点的一跳好友都收集到该节点上。即便根据邓巴的“150定律”,将一跳好友的个数,限制在150之内。那么图的占用空间,还是很可能会膨胀150倍。

这个时候,如果内存空间不够的话,GraphX为了容下所有的数据,会需要在节点之间,进行大量的Shuffle和Spill操作,使得后续的计算,变得非常慢。

其实这两个问题,在Spark的其它机器学习算法中,或多或少都会有,也是分布式计算系统中,经常面临的问题。但是在图计算中,它们是无法被忽略的问题,而且非常的严重。所以,这决定了GraphX需要大的内存,才能有良好的性能。

在正常情况下,128G内存,减掉8G的系统占用,剩下120G。这时配置每个Executor 60G内存,2个Core,每个Core分到30G的内存。这时不需要申请太多的Executor,经过合理的性能优化,全量关系链计算,可以运行成功。

性能优化

即便有了良好的模型和硬件保障,在面对QQ如此巨型的关系链时,依然需要熟练运用GraphX的技巧,并避开各种雷区,才能最终到达终点。简单总结两点:

图缓存:To Cache or not to Cache? That is a question

Spark和GraphX原本设计的精妙之处,亮点之一,便在于Cache,也就Persist(MEMORY_ONLY),或者Persist(MEMORY_AND_DISK)。可以把RDD和Graph,Cache到内存中,方便多次调用而无需重新计算。那么是否对所有的RDD,或者图,都Cache一下,是最佳的选择呢?答案是未必。

判断是否要Cache一个Graph或RDD,最简单和重要的标准,就是

该Graph,是否会在后续的过程中,被直接使用多次,包括迭代。

如果会,那么这个Graph就要被Persist,然后通过action触发

如果不会,那么反过来,最好把这个Graph直接unpersist掉

一个Graph被Cache的话,一般最终体现为2个RDD的Cache,一个是Edge,一个是Vertex,其占用量是非常巨大的。在整体空间有限的情况下,cache会导致内存的使用量大大加剧,引发多次GC和重算,反而会拖慢速度。在QQ全量的关系链计算,一个全量图是非常大的,因此如果在一个图没被多次使用,那么先unpersist,再返回给下一个计算步骤,反而成了最佳实践。

示例代码如下:

val oneNbrGraph = computeOneNbr(originalGraph)

oneNbrGraph.unpersist()

val resultRdd = oneNbrGraph.triplets.map {

…………

}

当然,既然unpersist了,切记 它只能被再用一次了。

分区策略:EdgePartition2D

对GraphX有所了解的人,应该都知道,有4种分区的策略,而其中性能最好的,莫过于EdgePartition2D这种边分区策略。但是由于QQ全量的关系链非常的大,所以,如果先用默认策略,构造了图,再调用partitionBy的方法来改变分区策略,那么会多一步代价非常高的计算。

因此,为了减少不必要的计算步骤,我们建议在构造图之前,先对Edge使用该策略进行划分,再用划分好的Edge RDD,进行图构建。 示例代码如下:

``` val edgesRepartitionRdd = edgeRdd.map { case (src, dst) => val pid = PartitionStrategy.EdgePartition2D.getPartition(src, dst, partitionNum) (pid, (src, dst)) }.partitionBy(new HashPartitioner(partitionNum)).map { case (pid, (src, dst)) => Edge(src, dst, 1) }

val g = Graph.fromEdges(

edgesRepartitionRdd,

...

)

```

当然,有个非常重要的Hint别忘记:partitionNum必须是平方数,才能达到最佳的性能。

最终效果

经过反复多次优化之后,在高配置集群的资源充足情况下,使用如下配置项:

|配置项|配置值| |---|---| |executors | 360 | |executor-cores | 2 | |executor-memory | 50g | |并行度 | 10000 |

总共消耗18T的内存,可以在2个半小时左右,完成整个QQ关系链的计算。BTW:

这个配置其实只是可运行配置,并非最佳配置。如果内存有512G,每个Executor配到80G,每个机器6个Executor,每个Executor 4个Core,应该能有更好的表现。可以进一步减少Executor的数目。

集群硬盘是SATA盘,而非SSD硬盘。在整个计算过程中,由于QQ的关系链实在庞大,中间的过程也比较复杂,所以不推荐用MEMORYONLY的选项,一旦内存充不下,重算的代价非常高,所以建议用MEMORYAND_DISK。而但是用这种方式意味着和硬盘的通信是必不可少的,因此如果硬盘能换成SSD的高性能盘,对整体性能会有很大提升。

在集群处于正常负荷的情况下,资源充足时,GraphX的任务不发生重跑时,作业可以在2小时10分之内,完成全量计算。但这是在运气最佳,没有任何Task发生重跑的情况下的表现。一旦有任务Task失败,Spark会自动重跑,但是整个计算过程会变得非常长,即便是很少的2-3个Task失败,也会将计算过程,延长到3个多小时甚至更多,这是因为GraphX的Failover没做好,而且在有多次迭代的时候,这个现象会更加严重。

总结和展望

整个的优化过程,貌似风轻云淡,但是中间经过了反复调优,多次在0.1的抽样数据和1.0的全量数据之间切换,优化每一步的操作,将硬件和GraphX的性能压榨到极致,才最终得到这个结果。

在这个过程中,我们发现无论应用层再怎么优化,核心层的软肋,始终制约着上层算法的性能。包括 Graph中最大限度的预创建和 RDD Cache的激进使用等问题,都会导致性能和稳定性不足,使得很多算法在腾讯级别的图数据下,显得捉襟见肘。其实这也难怪,GraphX的代码,从1.3版本开始,便已经一直没有变动,基本是在吃Core优化的红利,沾光提高性能,没有任何实质性的改进,如果要继续使用,在核心上必须有所提升才行。

腾讯作为拥有国内最大的关系链,在图计算的领域,无论是处于框架,模型,还是存储,都大有可为,可以做很多的事情。腾讯的数据平台部,作为公司的大数据支撑平台,欢迎在这方面有兴趣的业界同仁,和我们进行更多的合作和交流,共同在腾讯关系链上,玩出更多社交智能的火花。

欢迎加入本站公开兴趣群

软件开发技术群

兴趣范围包括:Java,C/C++,Python,PHP,Ruby,shell等各种语言开发经验交流,各种框架使用,外包项目机会,学习、培训、跳槽等交流

QQ群:26931708

Hadoop源代码研究群

兴趣范围包括:Hadoop源代码解读,改进,优化,分布式系统场景定制,与Hadoop有关的各种开源项目,总之就是玩转Hadoop

QQ群:288410967

转载:四两拨千斤:借助Spark GraphX将QQ千亿关系链计算提速20倍的更多相关文章

  1. 你好,C++(15)四两拨千斤——3.9 指向内存位置的指针

    3.9  指向内存位置的指针 一天,两个变量在街上遇到了: “老兄,你家住哪儿啊?改天找你玩儿去.” “哦,我家在静态存储区的0x0049A024号,你家呢?” “我家在动态存储区的0x0022FF0 ...

  2. 四两拨千斤式的攻击!如何应对Memcache服务器漏洞所带来的DDoS攻击?

    本文由  网易云发布. 近日,媒体曝光Memcache服务器一个漏洞,犯罪分子可利用Memcache服务器通过非常少的计算资源发动超大规模的DDoS攻击.该漏洞是Memcache开发人员对UDP协议支 ...

  3. 四两拨千斤,ARM是如何运作、靠什么赚钱的

    在智能手机.平板大行其道的今天,ARM这个名字我们几乎每天都要见到或者听到几次,作为编辑的我更是如此,每天涉及到的新闻总是或多或少跟ARM扯上关系,它还与Intel.AMD.NVIDA等公司有说不清道 ...

  4. 四两拨千斤——你不知道的VScode编码TypeScript的技巧

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://blog.bitsrc 如果你体验过JAVA这种强类型语言带来的便利,包括其丰富的 ...

  5. Java压测之四两拨千斤

    压测之四两拨千斤核心观念: 1.传统的http请求肯定不能用于压测,原因是请求一次,响应一次,而响应数据同时占用了客户端的带宽,故此,客户端请求后,不需要接受响应,让服务器单相思去. 2.寻找可以令服 ...

  6. [XSS防御]HttpOnly之四两拨千斤

    今天看了<白帽子讲web安全>一书,顺便记录一下,HttpOnly的设置 httponly的设置值为 TRUE 时,使得Javascript无法获取到该值,有效地防御了XSS打管理员的 c ...

  7. 明风:分布式图计算的平台Spark GraphX 在淘宝的实践

    快刀初试:Spark GraphX在淘宝的实践 作者:明风 (本文由团队中梧苇和我一起撰写,并由团队中的林岳,岩岫,世仪等多人Review,发表于程序员的8月刊,由于篇幅原因,略作删减,本文为完整版) ...

  8. 十、spark graphx的scala示例

    简介 spark graphx官网:http://spark.apache.org/docs/latest/graphx-programming-guide.html#overview spark g ...

  9. 转载:Spark GraphX详解

    1.GraphX介绍 1.1 GraphX应用背景 Spark GraphX是一个分布式图处理框架,它是基于Spark平台提供对图计算和图挖掘简洁易用的而丰富的接口,极大的方便了对分布式图处理的需求. ...

随机推荐

  1. AVI文件格式

    AVI文件采用的是RIFF文件结构方式.波形音频wave,MIDI和数字视频AVI都采用这种格式存储. AVI文件的整体结构如下图所示 构造RIFF文件的基本单元叫做数据块(Chunk),每个数据块包 ...

  2. head 与 tail

    head head [-n] 数字『文件』 显示前面n行 例如 head -n 3 test 显示 test 文件的前 3 行,也可以写作 head -3 test 比较有趣的是 -n 后面的数字,可 ...

  3. JavaScript之轮播图

    (1)html <div class="box" id="box"> <ul class="uls" id="u ...

  4. CSS-3D动画笔记

    3D 在2d的基础上添加 z 轴的变化 3D 位移:在2d的基础上添加 translateZ(),或者使用translate3d() translateZ():以方框中心为原点,变大 3D 缩放:在2 ...

  5. k8s维护常用命令

    k8s维护 1. 不可调度 kubectl cordon k8s-node-1 kubectl uncordon k8s-node-1 #取消 2.驱逐已经运行的业务容器 kubectl drain ...

  6. [LeetCode] 78. 子集 ☆☆☆(回溯)

    描述 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums = [1,2,3]输出:[ [3],  [1],  [2] ...

  7. mybatis添加sql打印功能

    添加配置文件: mybatis-config.xml <?xml version="1.0" encoding="UTF-8"?> <!DOC ...

  8. SQL SERVER升级2017

    SQL SERVER升级2017 摘要 本文只介绍了SQL SERVER升级到2017(在简单环境下),分为开始升级前的检查事项,升级操作步骤,升级后对新实例的配置. 检查事项 1.检查当前版本是否可 ...

  9. WIN2K8R2安装MySQL5.7及Tomcat8.5

    一.WIN2K8R2操作系统配置: 1.激活: 2.关闭防火墙: 3.打开远程桌面: 4.设置密码永不过期: 5.update:

  10. 基于tornado---异步并发接口

    1.目的 由于有多个程序和脚本需要对mysql进行读写数据库,每次在脚本中进行数据库的连接.用cursor进行操作过于麻烦,因此希望可以有一个脚本开放接口,只需要传入sql语句,就可以返回结果回来.因 ...