1. 简介
图处理对社交网络、广告等众多应用都非常有用。在大数据场景中,我们需要一个工具来分配处理负载。
在本教程中,我们将使用Java中的Apache Spark加载并探索图的各种可能性,为了避免复杂的结构,我们将使用一个简单且高级的Apache Spark图API:GraphFrames API。
2. 图
首先,我们来定义一下图及其组成部分;图是一种包含边和顶点的数据结构,边承载着表示顶点之间关系的信息。
顶点是n维空间中的点,边根据顶点之间的关系连接顶点:
上图是一个社交网络的例子,我们可以看到用字母表示的顶点,以及顶点之间承载关系的边。
3. Maven设置
现在,让我们通过设置Maven配置来启动项目。
让我们添加spark-graphx 2.11、graphframes和spark-sql 2.11:
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-graphx_2.11</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>graphframes</groupId>
<artifactId>graphframes</artifactId>
<version>0.7.0-spark2.4-s_2.11</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.4.4</version>
</dependency>
这些工件版本支持Scala 2.11。
另外,GraphFrames恰好不在Maven Central中,因此,我们来添加所需的Maven仓库:
<repositories>
<repository>
<id>SparkPackagesRepo</id>
<url>http://dl.bintray.com/spark-packages/maven</url>
</repository>
</repositories>
4. Spark配置
为了使用GraphFrames,我们需要下载Hadoop并定义HADOOP_HOME环境变量。
如果操作系统是Windows,我们还将适当的winutils.exe下载到HADOOP_HOME/bin文件夹。
接下来,让我们通过创建基本配置来开始我们的代码:
SparkConf sparkConf = new SparkConf()
.setAppName("SparkGraphFrames")
.setMaster("local[*]");
JavaSparkContext javaSparkContext = new JavaSparkContext(sparkConf);
我们还需要创建一个SparkSession:
SparkSession session = SparkSession.builder()
.appName("SparkGraphFrameSample")
.config("spark.sql.warehouse.dir", "/file:C:/temp")
.sparkContext(javaSparkContext.sc())
.master("local[*]")
.getOrCreate();
5. 图构建
现在,一切准备就绪,可以开始编写主代码了。首先,定义顶点和边的实体,并创建GraphFrame实例。
我们将研究假设的社交网络中的用户之间的关系。
5.1 数据
首先,对于这个例子,我们将两个实体定义为User和Relationship:
public class User {
private Long id;
private String name;
// constructor, getters and setters
}
public class Relationship implements Serializable {
private String type;
private String src;
private String dst;
private UUID id;
public Relationship(String type, String src, String dst) {
this.type = type;
this.src = src;
this.dst = dst;
this.id = UUID.randomUUID();
}
// getters and setters
}
接下来,让我们定义一些User和Relationship实例:
List<User> users = new ArrayList<>();
users.add(new User(1L, "John"));
users.add(new User(2L, "Martin"));
users.add(new User(3L, "Peter"));
users.add(new User(4L, "Alicia"));
List<Relationship> relationships = new ArrayList<>();
relationships.add(new Relationship("Friend", "1", "2"));
relationships.add(new Relationship("Following", "1", "4"));
relationships.add(new Relationship("Friend", "2", "4"));
relationships.add(new Relationship("Relative", "3", "1"));
relationships.add(new Relationship("Relative", "3", "4"));
5.2 GraphFrame实例
现在,为了创建和操作我们的关系图,我们将创建一个GraphFrame实例。GraphFrame构造函数需要两个Dataset<Row>实例,第一个表示顶点,第二个表示边:
Dataset<Row> userDataset = session.createDataFrame(users, User.class);
Dataset<Row> relationshipDataset = session.createDataFrame(relationships, Relation.class);
GraphFrame graph = new GraphFrame(userDataframe, relationshipDataframe);
最后,我们将在控制台中记录我们的顶点和边以查看它的外观:
graph.vertices().show();
graph.edges().show();
+---+------+
| id| name|
+---+------+
| 1| John|
| 2|Martin|
| 3| Peter|
| 4|Alicia|
+---+------+
+---+--------------------+---+---------+
|dst| id|src| type|
+---+--------------------+---+---------+
| 2|622da83f-fb18-484...| 1| Friend|
| 4|c6dde409-c89d-490...| 1|Following|
| 4|360d06e1-4e9b-4ec...| 2| Friend|
| 1|de5e738e-c958-4e0...| 3| Relative|
| 4|d96b045a-6320-4a6...| 3| Relative|
+---+--------------------+---+---------+
6. 图运算符
现在我们有了一个GraphFrame实例,让我们看看可以用它做什么。
6.1 过滤
GraphFrames允许我们通过查询过滤边和顶点。
接下来,让我们通过User上的name属性来过滤顶点:
graph.vertices().filter("name = 'Martin'").show();
在控制台我们可以看到结果:
+---+------+
| id| name|
+---+------+
| 2|Martin|
+---+------+
另外,我们可以通过调用filterEdges或filterVertices直接在图上进行过滤:
graph.filterEdges("type = 'Friend'")
.dropIsolatedVertices().vertices().show();
现在,由于我们过滤了边,可能仍然会有一些孤立的顶点。因此,我们将调用dropIsolatedVertices()。
因此,我们有一个子图,仍然是一个GraphFrame实例,仅包含具有“Friend”状态的关系:
+---+------+
| id| name|
+---+------+
| 1| John|
| 2|Martin|
| 4|Alicia|
+---+------+
6.2 度
另一个有趣的特征集是度运算集,这些运算返回与每个顶点关联的边数。
degrees运算仅返回每个顶点所有边的数量;另一方面,inDegrees运算仅计算入边数量,outDegrees运算仅计算出边数量。
让我们计算一下图中所有顶点的传入度:
graph.inDegrees().show();
因此,我们有一个GraphFrame,它显示了每个顶点的传入边的数量,不包括没有传入边的边:
+---+--------+
| id|inDegree|
+---+--------+
| 1| 1|
| 4| 3|
| 2| 1|
+---+--------+
7. 图算法
GraphFrames还提供了可立即使用的流行算法-让我们来看看其中的一些。
7.1 页面排名
Page Rank算法对到达顶点的传入边进行加权并将其转换为分数。
这个想法是,每个传入的边代表一个认可,并使顶点在给定的图中更加相关。
例如,在社交网络中,如果一个人被很多人关注,那么他或她的排名就会很高。
运行页面排名算法非常简单:
graph.pageRank()
.maxIter(20)
.resetProbability(0.15)
.run()
.vertices()
.show();
要配置此算法,我们只需要提供:
- maxIter:运行页面排名的迭代次数,建议为20,太少会降低质量,太多会降低性能
- resetProbability:随机重置概率(alpha),该值越低,获胜者和失败者之间的分数差距就越大;有效范围是0到1,通常,0.15是一个不错的分数
响应是一个类似的GraphFrame,但这次我们看到一个额外的列给出了每个顶点的页面排名:
+---+------+------------------+
| id| name| pagerank|
+---+------+------------------+
| 4|Alicia|1.9393230468864597|
| 3| Peter|0.4848822786454427|
| 1| John|0.7272991738542318|
| 2|Martin| 0.848495500613866|
+---+------+------------------+
在我们的图中,Alicia是最相关的顶点,其次是Martin和John。
7.2 连通分量
连通分量算法用于查找孤立簇或孤立子图,这些簇是图中连通顶点的集合,其中每个顶点都可以从同一集合中的任何其他顶点到达。
我们可以通过connectedComponents()方法调用不带任何参数的算法:
graph.connectedComponents().run().show();
该算法返回一个包含每个顶点以及每个顶点所连接的组件的GraphFrame:
+---+------+------------+
| id| name| component|
+---+------+------------+
| 1| John|154618822656|
| 2|Martin|154618822656|
| 3| Peter|154618822656|
| 4|Alicia|154618822656|
+---+------+------------+
我们的图只有一个组件-这意味着我们没有孤立的子图,该组件有一个自动生成的ID,在本例中为154618822656。
尽管这里多了一列组件ID,但我们的图仍然相同。
7.3 三角形计数
三角形计数通常用于社交网络图中的社群检测和计数,三角形是由三个顶点组成的集合,每个顶点都与三角形中的其他两个顶点存在关联。
在社交网络社区中,很容易找到大量相互连接的三角形。
我们可以轻松地直接从GraphFrame实例执行三角形计数:
graph.triangleCount().run().show();
该算法还返回一个GraphFrame,其中包含通过每个顶点的三角形的数量。
+-----+---+------+
|count| id| name|
+-----+---+------+
| 1| 3| Peter|
| 2| 1| John|
| 2| 4|Alicia|
| 1| 2|Martin|
+-----+---+------+
8. 总结
Apache Spark是一款出色的工具,能够以优化的分布式方式计算大量数据。此外,GraphFrames库使我们能够轻松地在Spark上分发图操作。
Post Directory
