Spark SQL可以使用其JDBC / ODBC或命令行界面充当分布式查询引擎。在这个模式下,用户或应用程序可以直接与Spark SQL交互以运行SQL查询,而无需编写任何代码。
对应HiveServer2 于Hive 1.2.1中的。可以使用Spark或Hive附带的beeline脚本测试JDBC服务器
Spark目录中运行命令:./sbin/start-thriftserver.sh
这个脚本接受所有bin/spark-submit命令行选项,以及--hiveconf指定Hive属性的选项。你可以运行./sbin/start-thriftserver.sh --help帮助查看所有可用选项的完整列表。默认情况下,服务器侦听localhost:10000。你可以通过任一环境变量覆盖此行为
即:
export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh \
--master <master-uri> \
或系统属性:
./sbin/start-thriftserver.sh \
--hiveconf hive.server2.thrift.port=<listening-port> \
--hiveconf hive.server2.thrift.bind.host=<listening-host> \
--master <master-uri>
你现在可以使用beeline来测试Thrift JDBC / ODBC服务器:
./bin/beeline
使用以下方式直接连接到JDBC / ODBC服务器:
beeline> !connect jdbc:hive2://localhost:10000
直线会询问你的用户名和密码。在非安全模式下,只需在您的计算机上输入用户名和空白密码即可。
你也可以使用Hive附带的beeline脚本。
Thrift JDBC服务器还支持通过HTTP传输发送thrift RPC消息。使用以下设置将HTTP模式作为系统属性启用或在hive-site.xml文件中启用conf/:
hive.server2.transport.mode - Set this to value: http
hive.server2.thrift.http.port - HTTP port number to listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice
要进行测试,使用beeline以http模式连接到JDBC / ODBC服务器:
beeline> !connect jdbc:hive2://<host>:<port>/<database>?hive.server2.transport.mode=http;hive.server2.thrift.http.path=<http_endpoint>
Spark SQL CLI是一种方便的工具,可以在本地模式下运行Hive Metastore服务并执行从命令行输入的查询。注意,Spark SQL CLI无法与Thrift JDBC服务器通信。
要启动Spark SQL CLI,请在Spark目录中运行以下命令:
./bin/spark-sql
Spark中的Apache Arrow
Apache Arrow是一种内存中的列式数据格式,在Spark中用于在JVM和Python进程之间高效地传输数据。这对于使用Pandas / NumPy数据的Python用户来说是最有益的。它的使用不是自动的,可能需要对配置或代码进行一些小的更改才能充分利用并确保兼容性。
如果使用pip安装PySpark,则可以使用该命令将PyArrow作为SQL模块的额外依赖项引入pip install pyspark[sql]。否则,必须确保在所有群集节点上安装并可用PyArrow。可以使用conda-forge通道中的pip或conda进行安装。
conda
# conda install -c conda-forge pyarrow
Pip
# pip install pyarrow
使用调用将Spark DataFrame转换为Pandas DataFrame toPandas()时以及使用Pandas DataFrame 创建Spark DataFrame时, Arrow可用作优化createDataFrame(pandas_df)。要在执行这些调用时使用Arrow,用户需要首先将Spark配置“spark.sql.execution.arrow.enabled”设置为“true”。如果在Spark中的实际计算之前发生错误,则由’spark.sql.execution.arrow.enabled’启用的优化可以自动回退到非Arrow优化实现。这可以通过’spark.sql.execution.arrow.fallback.enabled’来控制。
import numpy as np
import pandas as pd
spark.conf.set("spark.sql.execution.arrow.enabled", "true")
#生成Pandas DataFrame
pdf = pd.DataFrame(np.random.rand(100, 3))
# spark 从Pandas DataFrame创建Spark DataFrame
df = spark.createDataFrame(pdf)
result_pdf = df.select("*").toPandas()
注意,即使使用Arrow,也会toPandas()导致将DataFrame中的所有记录收集到驱动程序中,并且应该在一小部分数据上完成。当前不支持所有Spark数据类型,如果列具有不受支持的类型,则可能引发错误,请参阅支持的SQL类型。如果在此期间发生错误createDataFrame(),Spark将回退以创建没有Arrow的DataFrame。
Pandas UDF是用户定义的函数,由Spark使用Arrow执行传输数据和Pandas以处理数据。使用关键字pandas_udf作为装饰器或包装函数来定义Pandas UDF ,不需要其他配置。目前,有两种类型的Pandas UDF:Scalar和Grouped Map。
标量Pandas UDF用于矢量化标量操作。可以使用select和withColumn。Python函数应该pandas.Series作为输入并返回pandas.Series相同长度的a。在内部,Spark将执行Pandas UDF,方法是将列拆分为批次,并将每个批次的函数作为数据的子集调用,然后将结果连接在一起。
import pandas as pd
from pyspark.sql.functions import col, pandas_udf
from pyspark.sql.types import LongType
# 声明函数,并创建UDF函数
def multiply_func(a, b):
return a * b
multiply = pandas_udf(multiply_func, returnType=LongType())
# pandas_udf 的函数应该能够使用本地Pandas数据
x = pd.Series([1, 2, 3])
print(multiply_func(x, x))
# 0 1
# 1 4
# 2 9
# dtype: int64
# Create a Spark DataFrame, 'spark' is an existing SparkSession
df = spark.createDataFrame(pd.DataFrame(x, columns=["x"]))
# 执行功能作为矢量UDF
df.select(multiply(col("x"), col("x"))).show()
# +-------------------+
# |multiply_func(x, x)|
# +-------------------+
# | 1|
# | 4|
# | 9|
# +-------------------+
使用分组映射Pandas UDF groupBy().apply()实现“split-apply-combine”模式。
Split-apply-combine包含三个步骤:
- 使用将数据拆分为组DataFrame.groupBy。
- 在每个组上应用一个功能。该函数的输入和输出都是pandas.DataFrame。输入数据包含每个组的所有行和列。
- 将结果合并为一个新的DataFrame。
# 一个Python函数,用于定义每个组的计算。
# StructType对象或定义输出的模式的字符串DataFrame。
pandas.DataFrame如果指定为字符串,则返回的列标签必须与定义的输出模式中的字段名称匹配,或者,如果不是字符串,则必须按位置匹配字段数据类型,例如整数索引。请
参阅pandas.DataFrame ,了解如何在构造时标记列pandas.DataFrame。
注意,在应用函数之前,组的所有数据都将加载到内存中。这可能导致内存不足异常,尤其是在组大小偏斜的情况下。maxRecordsPerBatch的配置 不适用于组,并且由用户决定分组数据是否适合可用内存。
示例:如何使用groupby().apply()从组中的每个值中减去平均值
from pyspark.sql.functions import pandas_udf, PandasUDFType
df = spark.createDataFrame(
[(1, 1.0), (1, 2.0), (2, 3.0), (2, 5.0), (2, 10.0)],
("id", "v"))
@pandas_udf("id long, v double", PandasUDFType.GROUPED_MAP)
def subtract_mean(pdf):
# pdf is a pandas.DataFrame
v = pdf.v
return pdf.assign(v=v - v.mean())
df.groupby("id").apply(subtract_mean).show()
# +---+----+
# | id| v|
# +---+----+
# | 1|-0.5|
# | 1| 0.5|
# | 2|-3.0|
# | 2|-1.0|
# | 2| 4.0|
# +---+----+
分组聚合Pandas UDF类似于Spark聚合函数。分组聚合Pandas UDF与groupBy().agg()和pyspark.sql.Window 一起使用。它定义了从一个或多个pandas.Series 到标量值的聚合,其中每个聚合pandas.Series表示组或窗口中的列。
注意,此类型的UDF不支持部分聚合,组或窗口的所有数据都将加载到内存中。此外,目前只有Grouped聚合Pandas UDF支持无界窗口。
如何使用此类型的UDF来计算groupBy和窗口操作的平均值:
from pyspark.sql.functions import pandas_udf, PandasUDFType
from pyspark.sql import Window
df = spark.createDataFrame(
[(1, 1.0), (1, 2.0), (2, 3.0), (2, 5.0), (2, 10.0)],
("id", "v"))
@pandas_udf("double", PandasUDFType.GROUPED_AGG)
def mean_udf(v):
return v.mean()
df.groupby("id").agg(mean_udf(df['v'])).show()
# +---+-----------+
# | id|mean_udf(v)|
# +---+-----------+
# | 1| 1.5|
# | 2| 6.0|
# +---+-----------+
w = Window \
.partitionBy('id') \
.rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)
df.withColumn('mean_v', mean_udf(df['v']).over(w)).show()
# +---+----+------+
# | id| v|mean_v|
# +---+----+------+
# | 1| 1.0| 1.5|
# | 1| 2.0| 1.5|
# | 2| 3.0| 6.0|
# | 2| 5.0| 6.0|
# | 2|10.0| 6.0|
# +---+----+------+
使用说明
支持的SQL类型
目前,所有Spark SQL数据类型是基于箭转换,除了支持MapType, ArrayType中TimestampType和嵌套StructType。BinaryType仅当安装的PyArrow等于或高于0.10.0时才支持。
设置箭头批量大小
Spark中的数据分区将转换为箭头记录批次,这可能会暂时导致JVM中的高内存使用量。为了避免可能的内存不足异常,可以通过将conf“spark.sql.execution.arrow.maxRecordsPerBatch”设置为一个整数来调整箭头记录批次的大小,该整数将确定每个批次的最大行数。默认值为每批10,000个记录。如果列数很大,则应相应地调整该值。使用此限制,每个数据分区将被制成一个或多个记录批次以进行处理。
带时区语义的时间戳
Spark在内部将时间戳存储为UTC值,并且在没有指定时区的情况下引入的时间戳数据将以本地时间转换为UTC,并具有微秒分辨率。在Spark中导出或显示时间戳数据时,会话时区用于本地化时间戳值。会话时区使用配置'spark.sql.session.timeZone'设置,如果未设置,将默认为JVM系统本地时区。Pandas使用datetime64具有纳秒分辨率的类型datetime64[ns],并且每列具有可选的时区。
当时间戳数据从Spark传输到Pandas时,它将转换为纳秒,每列将转换为Spark会话时区,然后本地化到该时区,这将删除时区并将值显示为本地时间。调用toPandas()或pandas_udf使用timestamp列时会发生这种情况。
当时间戳数据从Pandas传输到Spark时,它将转换为UTC微秒。createDataFrame使用Pandas DataFrame 调用或从a返回时间戳时 会发生这种情况pandas_udf。这些转换是自动完成的,以确保Spark具有预期格式的数据,因此不必自己进行任何这些转换。任何纳秒值都将被截断。
注意,标准UDF(非Pandas)会将时间戳数据作为Python日期时间对象加载,这与Pandas时间戳不同。在pandas_udfs中使用时间戳时,建议使用Pandas时间序列功能以获得最佳性能