Kerberos 认证
本集群启用了Kerberos 认证,使用 Spark 客户端前需先完成认证。管理员可以使用集群成员服务器上的 spark keytab 进行 Kerberos 认证,从而获得 Spark 集群操作权限,用户需要先进行认证才能对集群进行操作,否则会报认证失败异常。
在命令行中输入如下命令即可完成Kerberos 认证:
klist -kt /etc/security/keytabs/spark.keytab | awk '{print $4}' | grep -e 'spark.*@.*' | head -n 1
kinit -kt /etc/security/keytabs/spark.keytab <principal>其中klist -kt 输出的第 4 列为 principal(主体名),通过 grep 过滤出 spark 用户的 principal 后用于 kinit 认证。
认证完成后可通过klist 命令查看当前票据信息,票据过期需重新认证。
spark-submit 提交作业
spark-submit 是 Spark 作业的统一提交入口,可提交 Java/Scala(jar 包)、Python 脚本等应用,基本用法如下:
spark-submit \
--master yarn \
--deploy-mode client \
--class <main-class> \
--name <application-name> \
--queue <yarn-queue> \
--num-executors <num> \
--executor-memory <size> \
--executor-cores <num> \
--driver-memory <size> \
--conf <key>=<value> \
<application-jar> [application-arguments]主要参数说明如下表所示:
| 参数 | 说明 |
|---|---|
| --master | 集群管理器地址,本集群取值yarn(提交至 YARN);还支持 local(本地模式)、spark://host:port(Standalone)等 |
| --deploy-mode | 部署模式:client(Driver 运行在提交进程本地)或 cluster(Driver 运行在 YARN ApplicationMaster 中),默认 client |
| --class | Java/Scala 应用的主类全限定名 |
| --name | 应用名称,显示在YARN 与 Spark Web UI 中 |
| --queue | 提交至的YARN 队列 |
| --num-executors | 申请的Executor 数量 |
| --executor-memory | 单个Executor 内存,如 4g |
| --executor-cores | 单个Executor 核数 |
| --driver-memory | Driver 内存,如 2g |
| --conf | 以key=value 形式设置 Spark 配置项,可多次使用 |
| --jars | 额外依赖jar 包,多个以逗号分隔,随作业分发到各 Executor |
| --py-files | Python 作业附加的 .py/.zip/.egg 依赖 |
说明
client 模式适合交互与调试,Driver 日志直接输出到提交端终端;cluster 模式适合生产作业,Driver 随 YARN 调度运行,提交端断开后作业继续运行,日志需通过 YARN 查看。
注意
若集群启用了Executor 动态分配(spark.dynamicAllocation.enabled=true),则 --num-executors 与 spark.executor.instances 配置会被忽略,Executor 数量由动态分配策略按负载决定,两者不要同时使用,避免配置不生效造成资源误判。
运行示例:SparkPi
使用Spark 自带示例程序验证集群可用性,在命令行中输入如下命令:
spark-submit --master yarn --deploy-mode client \
--class org.apache.spark.examples.SparkPi \
$SPARK_HOME/examples/jars/spark-examples_2.12-3.4.1.jar 100执行成功后终端输出类似如下内容:
Pi is roughly 3.1418599285819928可通过yarn application -list 查看 application 状态为 FINISHED:
yarn application -list -appStates FINISHEDspark-shell / pyspark 交互式环境
spark-shell 提供基于 Scala 的交互式编程环境,pyspark 提供基于 Python 的交互式环境,两者均在启动时自动创建 SparkSession(变量名 spark),适合交互式探索与调试。启动命令如下:
spark-shell --master yarn --deploy-mode client
pyspark --master yarn --deploy-mode clientspark-shell 中执行 WordCount 示例:
val text = spark.read.textFile("/tmp/input.txt")
val counts = text.flatMap(line => line.split(" ")).groupBy("value").count()
counts.show()pyspark 中执行简单 SQL 示例:
df = spark.createDataFrame([("a", 1), ("b", 2)], ["k", "v"])
df.createOrReplaceTempView("t")
spark.sql("select k, sum(v) from t group by k").show()spark-sql CLI
Spark SQL CLI 是一个交互式命令行客户端工具,可以连接 Hive 元存储服务,并执行输入的 SQL。常用方式为通过 -e 执行单条语句或 -f 执行脚本文件:
spark-sql -e "select count(*) from db_name.table_name"
spark-sql -f /path/to/script.sql # script.sql 为用户自建的SQL脚本文件spark-sql 的主要启动参数如下表所示(spark-sql --help 可查看完整列表,包含【options】与【cli option】两类,前者为 Spark 多客户端共用参数,后者为 Spark SQL CLI 独有参数):
| 参数 | 说明 |
|---|---|
| -e <sql> | 直接执行一条SQL 语句 |
| -f <file> | 执行SQL 脚本文件 |
| -i <file> | 初始化脚本,进入交互模式前执行 |
| --hiveconf k=v | 设置Hive 上下文配置项 |
| -d | 指定自定义资源目录 |
不带-e 或 -f 参数时,spark-sql 进入交互式 shell 模式,以分号(;)分隔命令。输入 quit; 或 exit; 退出客户端。交互模式下的常用命令如下表所示:
| 命令 | 说明 |
|---|---|
| quit; / exit; | 退出客户端 |
| use <database>; | 切换数据库 |
| show databases; / show tables; | 查看库与表列表 |
| describe <table>; | 查看表结构 |
| set <key>=<value>; | 设置会话参数 |
| add jar <path>; | 添加依赖jar |
| source <file>; | 执行脚本文件 |
注意
spark-sql 适合批量脚本与交互场景。本集群未部署 Spark ThriftServer,JDBC/Beeline 编程接入请统一使用 Kyuubi 服务(参见 Kyuubi 使用文档),通过 Beeline 连接 Kyuubi 的 JDBC 地址。
Spark SQL 语法支持
Spark SQL 语法分为 DDL、DML、数据检索(Data Retrieval)与辅助(Auxiliary)四类,主要语法清单如下表所示:
| 类别 | 语法 |
|---|---|
| DDL(数据定义) | CREATE/DROP/ALTER DATABASE、CREATE/DROP/ALTER TABLE、CREATE/DROP/ALTER VIEW、CREATE/DROP FUNCTION、REPAIR TABLE、TRUNCATE TABLE、USE DATABASE |
| DML(数据操作) | INSERT TABLE、INSERT OVERWRITE DIRECTORY、LOAD DATA |
| 数据检索 | SELECT(含 Common Table Expression、JOIN、WHERE、GROUP BY、HAVING、ORDER/SORT/DISTRIBUTE/CLUSTER BY、LIMIT、Window Function、PIVOT、LATERAL VIEW、TABLESAMPLE、Hints、Set Operators)、EXPLAIN |
| 辅助语法 | ADD/LIST FILE/JAR、ANALYZE TABLE、CACHE/UNCACHE TABLE、DESCRIBE DATABASE/TABLE/FUNCTION/QUERY、REFRESH TABLE/FUNCTION、SET/RESET、SHOW DATABASES/TABLES/PARTITIONS/COLUMNS/CREATE TABLE/TBLPROPERTIES/FUNCTIONS/VIEWS |
各语法的使用细节可参考Spark 官方文档的 SQL 语法章节。
Spark SQL 示例:企业雇员分析
以企业雇员分析为背景构建三张表:雇员基本信息表(employees_info)、雇员通信表(employees_contact)、公司部门表(department),演示建库建表、导入数据与分析查询的完整流程。
1、创建数据库及表:
CREATE DATABASE IF NOT EXISTS company;
USE company;
CREATE TABLE IF NOT EXISTS employees_info
(
id INT COMMENT '雇员ID',
name STRING COMMENT '雇员名称',
gender STRING COMMENT '性别',
dept_id INT COMMENT '部门ID',
grade STRING COMMENT '雇员工资等级',
salary DOUBLE COMMENT '雇员薪水',
deduction_rate DOUBLE COMMENT '雇员税率',
entry_date STRING COMMENT '入职日期',
other_info STRING COMMENT '额外信息'
)
USING PARQUET
COMMENT '员工信息基础表';
CREATE TABLE IF NOT EXISTS employees_contact
(
id INT COMMENT '雇员ID',
phone STRING COMMENT '雇员电话号',
email STRING COMMENT '雇员email'
)
USING PARQUET
COMMENT '雇员通信表';
CREATE TABLE IF NOT EXISTS department
(
dept_id INT COMMENT '部门ID',
dept_name STRING COMMENT '部门名称'
);2、导入数据(示例数据节选):
INSERT INTO employees_contact VALUES
(53275, '145939987', 'oqjunox@yahoo.com'),
(21430, '183727019', 'tmmprglpsy@gmail.com'),
(79254, '143213819', 'nxtay@outlook.com');
INSERT INTO employees_info VALUES
(7821, 'Meryl Wilson', 'm', 1, 'M1', 120129, 0.23, '2007-4-8',
'{"country": "china", "city": "beijing", "single": false}'),
(9130, 'Jacob Tracie', 'f', 2, 'M2', 15305, 0.53, '2002-2-11',
'{"country": "china", "city": "beijing", "single": false}');
INSERT INTO department VALUES (1, '测试'), (2, '开发'), (3, '产品');3、分析查询:
-- 查看各个部门雇员薪水的平均值
SELECT dept.dept_name, ROUND(AVG(emp.salary), 2)
FROM employees_info emp
JOIN department dept ON emp.dept_id = dept.dept_id
GROUP BY dept.dept_name;
-- 查看雇员中各个号段的数量
SELECT SUBSTRING(phone, 1, 3) AS phone_field, COUNT(phone) AS cnt
FROM employees_contact
GROUP BY SUBSTRING(phone, 1, 3);
-- 查看雇员额外信息中包含哪些内容
SELECT name, other_info AS info FROM employees_info;Scala SDK 开发
Spark 支持 Java/Scala/Python/R 语言进行应用开发。由于 Spark 本身主要由 Scala 语言开发,且 Scala 语言简洁易懂,推荐使用 Scala 语言开发。向工程 pom 文件添加依赖(版本与本集群一致,scala 为 2.12):
<properties>
<spark.version>3.4.1</spark.version>
<scala.version>2.12</scala.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
</dependencies>注意
该处引用的Spark Jar包需要是天翼云提供的$SPARK_HOME/jars下的Jar包
示例场景:有人员名单日志people.json,要求开发 Spark 应用实现筛选成年人(age >= 18)与统计总人数。
people.json 为示例数据文件,集群中不存在现成文件,需自行创建:在本地开发机任一目录(如工程 resources 目录)新建文本文件,命名为 people.json,将下面代码块中的 4 行内容原样复制进去保存即可(JSON Lines 格式,每行一个 JSON 对象,UTF-8 编码):
{"name":"Alice", "age":10}
{"name":"Jessica", "age":23}
{"name":"Albert", "age":16}
{"name":"Anna", "age":55}本地调试时,将该文件放到工程src/main/resources/ 目录下(代码中读取路径 src/main/resources/people.json 即对应此位置);集群运行时按"集群运行完整步骤"上传到 HDFS。
新建object 文件 SparkSQLExample:
package spark_sql_demo
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
object SparkSQLExample {
case class Person(name: String, age: Long)
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf()
val spark = SparkSession
.builder()
.appName("Spark SQL basic example")
.config(sparkConf)
.getOrCreate()
runExample(spark)
spark.stop()
}
private def runExample(spark: SparkSession): Unit = {
val df = spark.read.json("src/main/resources/people.json")
df.show()
import spark.implicits._
df.filter($"age" >= 18).show() // 筛选成年人
df.createOrReplaceTempView("people")
spark.sql("SELECT count(*) FROM people").show() // 统计总人数
}
}集群运行完整步骤(从零开始):
1、将上文的 4 行人员数据保存为本地文件 people.json;
2、上传到 HDFS(用户目录下,命令会自动创建 /user/<用户名>/people.json):
hdfs dfs -put people.json /user/<用户名>/ # 或简化为: hdfs dfs -put people.json people.json3、修改代码中的读取路径为 HDFS 路径:
val df = spark.read.json("hdfs:///user/<用户名>/people.json")4、maven 打包获得 spark_sql_demo-1.0-SNAPSHOT.jar,通过 spark-submit 运行:
spark-submit --class spark_sql_demo.SparkSQLExample \
spark_sql_demo-1.0-SNAPSHOT.jar注意
示例代码中读取路径src/main/resources/people.json 为本地开发路径,仅在 local 模式调试时有效。集群(yarn)模式下 Executor 节点访问不到提交机的本地文件,必须先按上述步骤将文件上传至 HDFS 并改为 HDFS 路径读取。
配置说明
Spark 的服务端与客户端配置文件位于 $SPARK_HOME/conf 目录下,主要配置文件说明如下表所示:
| 配置文件 | 说明 |
|---|---|
| spark-defaults.conf | Spark 参数默认值文件,每行 key=value,提交作业时自动加载,可被 --conf 覆盖 |
| spark-env.sh | 环境变量脚本,设置SPARK_CONF_DIR、YARN_CONF_DIR、SPARK_EXECUTOR_MEMORY、SPARK_DRIVER_MEMORY 等环境变量 |
| log4j2.properties | Spark 进程日志配置 |
常用配置参数及其默认值如下表所示(取自Spark 3.4.1 源码默认配置):
| 参数 | 默认值 | 说明 |
|---|---|---|
| spark.master | local[*] | 集群管理器,本集群固定为yarn |
| spark.driver.memory | 1g | Driver 进程内存 |
| spark.executor.memory | 1g | 单个Executor 内存 |
| spark.executor.cores | 1 | 单个Executor 核数 |
| spark.executor.instances | 无(动态分配) | Executor 数量 |
| spark.default.parallelism | 所有核心总数 | RDD 算子(如 reduceByKey)默认分区数 |
| spark.sql.shuffle.partitions | 200 | Spark SQL Join/聚合 Shuffle 默认分区数 |
| spark.memory.fraction | 0.6 | 执行与存储统一内存占堆内存的比例,其余为用户内存与预留 |
| spark.serializer | org.apache.spark.serializer.JavaSerializer | 序列化器,shuffle 数据量大时建议使用 KryoSerializer |
| spark.eventLog.enabled | false | 是否记录事件日志(本集群建议开启用于排障) |
| spark.eventLog.dir | file:/tmp/spark-events | 事件日志目录,生产环境建议配置为HDFS 路径 |
| spark.sql.adaptive.enabled | true | AQE 自适应查询执行开关(3.4 默认开启) |
| spark.sql.adaptive.skewJoin.enabled | true | AQE 倾斜 Join 自动优化开关 |
| spark.shuffle.file.buffer | 32k | Shuffle 写缓冲区大小 |
| spark.reducer.maxSizeInFlight | 48m | Reduce 端单次拉取 map 输出的大小上限 |
| spark.ui.port | 4040 | 应用Web UI 端口,被占用时自动递增 |
| spark.history.ui.port | 18080 | History Server Web UI 端口 |