1. 简介
图计算在社交网络、推荐系统、广告投放等场景中有着广泛应用。当数据量达到一定规模时,单机处理已无法满足性能需求,必须借助分布式计算框架来分担负载。
本文将介绍如何在 Java 环境下使用 Apache Spark 结合 GraphFrames 库进行图结构的构建与分析。相比底层的 GraphX,GraphFrames 提供了更高层次、更易用的 API,支持 DataFrame 和 SQL 操作,开发效率更高,适合快速实现图算法逻辑。
2. 图的基本概念
图(Graph)由顶点(Vertex)和边(Edge)组成。顶点表示实体(如用户、商品),边表示实体之间的关系(如关注、好友、交易)。
边是有方向的,通常包含一个“源顶点”(src)和“目标顶点”(dst),并可携带属性(如关系类型、权重)。顶点也可以携带属性,比如用户的姓名、年龄等。
下面是一个简单的社交网络示意图:
图中字母代表用户(顶点),箭头代表关系(边),例如 A → B 表示 A 关注 B。
3. Maven 依赖配置
使用 GraphFrames 需要引入 Spark 相关的核心依赖以及 GraphFrames 自身的包。注意:GraphFrames 并不在 Maven Central 仓库中,需要额外添加 Spark Packages 的仓库地址。
✅ 必须引入的依赖如下:
<dependencies>
<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>
</dependencies>
⚠️ 注意版本匹配:上述依赖基于 Scala 2.11 编译,且 Spark 版本为 2.4.x。若使用其他版本,请前往 Spark Packages 官网 查找对应版本。
还需添加仓库源:
<repositories>
<repository>
<id>SparkPackagesRepo</id>
<url>http://dl.bintray.com/spark-packages/maven</url>
</repository>
</repositories>
4. Spark 环境初始化
使用 GraphFrames 前需正确配置 Spark 运行环境。特别地,Windows 用户需要额外配置 Hadoop 环境,否则会报错。
4.1 Windows 踩坑提示
- 下载 Hadoop 发行版(如 3.0.0),解压后设置
HADOOP_HOME
环境变量。 - 将
winutils.exe
放入%HADOOP_HOME%\bin
目录,否则会提示 “Cannot run program winutils.exe”。
4.2 创建 Spark 上下文
SparkConf sparkConf = new SparkConf()
.setAppName("SparkGraphFrames")
.setMaster("local[*]"); // 本地模式,使用所有可用核心
JavaSparkContext javaSparkContext = new JavaSparkContext(sparkConf);
SparkSession session = SparkSession.builder()
.appName("SparkGraphFrameSample")
.config("spark.sql.warehouse.dir", "/file:C:/temp") // Windows 下必须指定本地路径
.sparkContext(javaSparkContext.sc())
.master("local[*]")
.getOrCreate();
✅ 建议:生产环境应根据集群情况调整 master
地址(如 spark://master:7077
)。
5. 图的构建
我们以一个假想的社交网络为例,演示如何从用户和关系数据构建图结构。
5.1 数据模型定义
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
// getter 和 setter 省略
}
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();
}
// getter 和 setter 省略
}
5.2 构建顶点与边 Dataset
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"));
Dataset<Row> userDataset = session.createDataFrame(users, User.class);
Dataset<Row> relationshipDataset = session.createDataFrame(relationships, Relationship.class);
5.3 创建 GraphFrame 实例
GraphFrame graph = new GraphFrame(userDataset, relationshipDataset);
打印顶点和边验证:
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. 图操作(Graph Operators)
GraphFrames 提供了一系列对图结构进行操作的方法,便于数据筛选和子图提取。
6.1 过滤操作(Filter)
支持对顶点或边进行 SQL 风格的条件过滤。
// 查询名为 Martin 的用户
graph.vertices().filter("name = 'Martin'").show();
输出:
+---+------+
| id| name|
+---+------+
| 2|Martin|
+---+------+
也可以直接在图上操作,过滤边后自动清理孤立顶点:
graph.filterEdges("type = 'Friend'")
.dropIsolatedVertices()
.vertices().show();
结果只保留“好友”关系涉及的用户:
+---+------+
| id| name|
+---+------+
| 1| John|
| 2|Martin|
| 4|Alicia|
+---+------+
✅ 技巧:dropIsolatedVertices()
能有效清理无连接的“脏”顶点,避免干扰后续计算。
6.2 度统计(Degrees)
度(Degree)反映顶点的连接活跃度,常用于识别关键节点。
degrees()
:总度数(入度 + 出度)inDegrees()
:入度(被多少人指向)outDegrees()
:出度(指向多少人)
示例:统计每个用户的被关注数(入度)
graph.inDegrees().show();
输出:
+---+--------+
| id|inDegree|
+---+--------+
| 1| 1|
| 4| 3|
| 2| 1|
+---+--------+
可见 Alicia 被关注最多(3 次),是社交网络中的“明星用户”。
7. 图算法(Graph Algorithms)
GraphFrames 内置多种经典图算法,开箱即用。
7.1 PageRank 算法
PageRank 用于衡量节点的重要性,核心思想是:被越多重要节点指向的节点,自身也越重要。
在社交网络中,PageRank 高的用户通常是意见领袖或核心人物。
graph.pageRank()
.maxIter(20) // 迭代次数,20 是推荐值
.resetProbability(0.15) // 随机跳转概率,0.15 是默认值
.run()
.vertices()
.show();
输出(含 pagerank 列):
+---+------+------------------+
| id| name| pagerank|
+---+------+------------------+
| 4|Alicia|1.9393230468864597|
| 3| Peter|0.4848822786454427|
| 1| John|0.7272991738542318|
| 2|Martin| 0.848495500613866|
+---+------+------------------+
结果表明 Alicia 是网络中最重要的人物,这与其高入度一致。
⚠️ 参数建议:
maxIter
过小会导致结果不收敛,过大影响性能。resetProbability
越小,评分差异越明显,但可能过度放大头部效应。
7.2 连通分量(Connected Components)
该算法识别图中相互可达的子图(社区)。每个连通分量内的任意两个顶点都存在路径相连。
graph.connectedComponents().run().show();
输出:
+---+------+------------+
| id| name| component|
+---+------+------------+
| 1| John|154618822656|
| 2|Martin|154618822656|
| 3| Peter|154618822656|
| 4|Alicia|154618822656|
+---+------+------------+
所有用户属于同一个连通分量,说明整个网络是连通的,没有孤立的小团体。
7.3 三角计数(Triangle Counting)
三角形指三个顶点两两相连形成的闭环。在社交网络中,三角形越多,说明社区结构越紧密(比如“你的好友也是我的好友”)。
graph.triangleCount().run().show();
输出:
+-----+---+------+
|count| id| name|
+-----+---+------+
| 1| 3| Peter|
| 2| 1| John|
| 2| 4|Alicia|
| 1| 2|Martin|
+-----+---+------+
John 和 Alicia 各参与了 2 个三角形,说明他们在社交圈中处于核心位置,促进了多个小团体的连接。
8. 总结
Apache Spark + GraphFrames 是处理大规模图数据的利器。相比原生 GraphX,GraphFrames 基于 DataFrame 构建,API 更简洁,与 Spark SQL 无缝集成,适合 Java/Scala 开发者快速实现图分析需求。
本文涵盖了图的构建、基本操作和常用算法,可作为实际项目中的参考模板。完整代码已托管至 GitHub:
https://github.com/baeldung/tutorials/tree/master/apache-spark