1. 简介

图计算在社交网络、推荐系统、广告投放等场景中有着广泛应用。当数据量达到一定规模时,单机处理已无法满足性能需求,必须借助分布式计算框架来分担负载。

本文将介绍如何在 Java 环境下使用 Apache Spark 结合 GraphFrames 库进行图结构的构建与分析。相比底层的 GraphX,GraphFrames 提供了更高层次、更易用的 API,支持 DataFrame 和 SQL 操作,开发效率更高,适合快速实现图算法逻辑。

2. 图的基本概念

图(Graph)由顶点(Vertex)边(Edge)组成。顶点表示实体(如用户、商品),边表示实体之间的关系(如关注、好友、交易)。

边是有方向的,通常包含一个“源顶点”(src)和“目标顶点”(dst),并可携带属性(如关系类型、权重)。顶点也可以携带属性,比如用户的姓名、年龄等。

下面是一个简单的社交网络示意图:

Graph Example 1

图中字母代表用户(顶点),箭头代表关系(边),例如 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


原始标题:Introduction to Spark Graph Processing with GraphFrames | Baeldung