基于MongoDB的SQL数据服务 原创 精华
1. 业务背景
某省高速集团为了更好的分析交易流水数据,掌控各个阶段的流水数据的及时性和准确性,新上了对账系统。通过对账系统可以很方便地对交易流水进行统计和查找。例如分析流水是否已出站,交易是否已上传部中心,交易是否在银行已记账,银行是否已划账,部中心是否已拆分,省中心是否已下载拆分数据,省中心是否已清分,省中心是否已拆账,流水是否已上传发票等等。
分析对账系统牵涉的数据,主要特性如下:
-
数据种类多。目前数据源多,接近20种,以后可能还会扩展。
-
数据量大。每种数据源,记录都不小,其中光拆分数据每月记录接近4000万,存储接近200G。
-
性能。各种分析统计性能要求高,实时统计。
-
大字段。多个数据源存在大字段,JSON串。如拆分记录中存在多个大字段,计费金额组合、路段编号组合、收费单元编号组合、业主编号组合、收费单元交易金额组合、收费单元时间组合等等都是按固定格式形成的序列数据,长度不固定。在使用中需要解析成具体数据,然后再统计分析。
-
模型复杂。多个数据源的关联复杂,不同数据源的分区分表规则各异。
-
数据的安全性。对账系统的数据源基本都是核心数据,在安全性,冗余性,一致性都有严格要求。
针对数据特性,项目组初步提出了三种方案:
- Hive。Hive是基于Hadoop的一个数据仓库工具,可以用来对数据提取、转化、加载、存储、查询和分析。
- 优点:
- 类SQL语法,提供快速开发的能力(简单、容易上手)。
- Hive的扩展性与集群的扩展性相同,通过集群的扩展获得性能的提升。
- 支持用户自定义函数,编写符合自己业务需求的函数。
- 缺点:
- 执行开销较大,任务运行时间较长,延时较高。
- 事务限制多,单行记录的频繁更新性能差。
- 调优比较困难,粒度较粗。
- Hive部署复杂,运营成本高。
- 优点:
- MySQL。MySQL是一个关系型数据库管理系统**,**最流行的关系型数据库管理系统之一。
-
优点:
- 易用性。使用最流行、最广泛的查询语言SQL,开发门槛低。
- 事务简单,使用方便,性能好。
- 部署简单,运营成本低。
-
缺点:
-
强数据模式,强制要求预先确定好数据表的列。模型变更约束多,修改表结构对业务影响大,大数据量情况下变更模型时间长。
-
性能瓶颈。大数据情况下的汇总、统计性能一般。拆分系统的统计一般都要10个小时以上,严重影响效率。
-
-
- MongoDB。MongoDB是一个基于分布式文件存储的数据库。
-
优点:
- 模式自由。在MongoDB 中存储的数据是无模式的文档,无需事先确定数据的文档属性,通过删除或修改集合中的文档属性,以实现高度灵活性。
- 性能高。分布式架构在处理海量数据时优势明显。
- 多副本。数据多副本提高数据的安全性。
- 分片模式。通过横向扩容的方式,为用户提供了近乎无限的空间。
-
缺点:
-
易用性不高。MongoDB使用的是非结构化的查询语言,对开发人员的亲和度不强。
-
运营、监控、维护成本较高。没有MySQL那样成熟的维护工具。
-
根据系统的实际情况,讨论决定采用MongoDB来存储数据,主要原因如下:
- 性能。对账系统需要支持实时查询和统计,延时要求低。Hive和MySQL在大数据情况下经测试查询时间长,没法达到要求。MongoDB经过测试,性能可以满足要求,基本上响应时间都在秒级。
- 模型扩展性。目前对账系统已经有接近20种数据源,以后还会新增数据源。Hive、MySQL中每种数据源都有独立的模型,模型之间通过主外键关联,新增数据源,模型变更复杂。MongoDB中把数据源作为文档的一个属性存储,避免了关联。
- 关联查询。对账系统的查询和统计牵涉到多种数据源的关联。在Hive、MySQL中每种数据源的分区分表规则不同,维护关联关系需要耗费很多的精力。MongoDB数据源只是文档的属性,关联关系维护简单。
选定了MongoDB作为对账系统的存储系统,项目组遇到一个现实问题。MongoDB不支持SQL查询语言,项目组开发人员大部分都是熟悉SQL,需要重新学习MongoDB的非结构化的查询语言。为了解决开发人员负担,项目组开发出SQL数据服务,开发人员通过数据服务提供的SQL支持来获取MongoDB的数据访问。
2. 技术实现
2.1 设计思路
MongoDB是一款文档型数据库,具有高性能、高可用、可水平扩展,且支持多种存储引擎,支持多种查询类型(比如文本检索、数据聚合查询等)。但是MongoDB原生是不支持SQL查询的,有没有办法使得MongDB支持SQL呢,答案是肯定的。
使得MongDB支持SQL的涉及到2个关键的开源组件/模块,即Spark Thrift Server和MongoDB Spark Connector。
Spark Thrift Server可以对外提供服务,客户端通过JDBC连接到服务后,可以向服务端提交SQL,服务端会解析SQL然后将其转化为Spark Job提交到Spark集群,从而转换为底层的基于Spark RDD的分布式计算,得到数据结果后返回给客户端。
MongoDB Spark Connector是一个集成MongoDB和Spark的模块,程序中引入该模块后,可以通过Spark来操作MongoDB,这里我们主要用它来通过Spark查询MongoDB的数据。
于是,可以对Spark Thrift Server进行二次开发,增加如下的功能:
- 集成MongoDB Spark Connector,可以将MongoDB的表转换为Spark Thrift Server中的DataFrame,然后将DataFrame注册为Spark Thrift Server中Spark SQL的global temp view,然后就可以通过JDBC客户端使用SQL来查询global temp view,从而达到查询MongoDB的数据的目的。
集成MongoDB查询的Spark Thrift Server启动流程:
JDBC客户端查询MongoDB交互流程:
2.2 Spark Thrift Server介绍
Spark Thrift Server是Spark项目当中的一个模块,它是Apache Hive的HiveServer2的Spark SQL端口,允许JDBC/ODBC客户端在Apache Spark上通过JDBC和ODBC协议执行SQL查询。 实际上它通过继承Hive的HiveServer2并巧妙地通过覆盖某些方法来实现将Hive SQL查询替换为Spark SQL查询。
github地址:https://github.com/apache/spark
2.3 MongoDB Spark Connector介绍
MongoDB Spark Connector是MongoDB和Spark的连接器,提供MongoDB和Apache Spark之间的集成。 通过连接器,可以访问所有用于MongoDB数据集的Spark库:用于SQL分析的DataSet/DataFrame(受益于自动模式推断)、Spark Streaming、Machine Learning和Spark Graph。 也可以通过Spark Shell来使用MongoDB Spark Connector。
关于MongoDB Spark Connector的更详细的介绍可以参考官方文档:https://www.mongodb.com/docs/spark-connector/current/
github地址:https://github.com/mongodb/mongo-spark
2.4 核心逻辑实现
2.4.1 下载Spark项目源码
从github上下载Spark源代码,并基于v2.4.3版本(也可以基于其他版本,这里以该版本为例)创建开发分支。
git clone git@github.com:apache/spark.git
git checkout v2.4.3
git checkout -b dev_v2.4.3
2.4.2 修改pom.xml
1). 修改spark项目根目录的pom.xml,根据需要修改maven和hadoop的版本,增加MongoDB Spark Connector的依赖。最后,需要增加sql/hive-thriftserver模块,否则将不会编译Spark Thrift Server(默认是不编译的)。
2). 修改sql/hive-thriftserver/pom.xml,该模块因为需要二次开发增加查询MongoDB的功能,故需要引入MongoDB Spark Connector的依赖。
2.4.3 新增UdfLoadUtils.scala
新增sql/hive-thriftserver/src/main/scala/org/apache/spark/sql/hive/thriftserver/server/UdfLoadUtils.scala
这个代码就是主要的新增逻辑,用于spark thrift server注册mongodb表和udf函数。
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.spark.sql.hive.thriftserver.server
import scala.collection.mutable.ArrayBuffer
import scala.io.Source
import java.io.File
import com.mongodb.spark._
import com.mongodb.spark.config.ReadConfig
import org.apache.spark.SparkConf
import org.apache.spark.internal.Logging
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
import org.apache.spark.sql.types._
/**
* @Description: Register spark udf and mongodb tables
* @author: lixinyang
* @Date: Created on 2022/03/31
* @version: v1.0
*/
object UdfLoadUtils extends Logging {
var udfConfigArray: Array[String] = null
var mongoTableConfigArray: Array[String] = null
var mongoTableDLLConfigArray: Array[String] = null
var mongoUri: String = null
var collection: String = null
/**
* Read config file and parse the configurations.
* @param spark SparkSession from Spark Thrift Server
*/
def init(spark: SparkSession): Unit = {
val sparkConf = spark.sparkContext.getConf
mongoUri = getSparkConfigValue(sparkConf, "spark.mongodb.input.uri")
if (mongoUri.isEmpty) throw new RuntimeException("spark.mongodb.input.uri not provided")
logInfo(s"=====mongoUri config: ${mongoUri}")
collection = getSparkConfigValue(sparkConf, "spark.mongodb.input.collection")
if (collection.isEmpty) throw new RuntimeException("spark.mongodb.input.collection not provided")
logInfo(s"=====collection config: ${collection}")
val udfConfig = getSparkConfigValue(sparkConf, "spark.udf.config")
if (udfConfig.isEmpty) throw new RuntimeException("spark.udf.config not provided")
logInfo(s"=====udf config: ${udfConfig}")
if (udfConfigArray == null) udfConfigArray = getConfigArray(udfConfig)
val mongoTableConfig = getSparkConfigValue(sparkConf, "spark.mongo.table.config")
if (mongoTableConfig.isEmpty) throw new RuntimeException("spark.mongo.table.config not provided")
logInfo(s"=====mongo table config: ${mongoTableConfig}")
if (mongoTableConfigArray == null) mongoTableConfigArray = getConfigArray(mongoTableConfig)
val mongoTableDLLConfig = getSparkConfigValue(sparkConf, "spark.mongo.tableDLL.config")
if (mongoTableDLLConfig.isEmpty) throw new RuntimeException("spark.mongo.tableDLL.config not provided")
logInfo(s"=====mongo table dll config: ${mongoTableDLLConfig}")
if (mongoTableDLLConfigArray == null) mongoTableDLLConfigArray = getConfigArray(mongoTableDLLConfig)
}
/**
* Register UDFs to Spark Thrift Server according to UDF configuration.
* @param spark SparkSession from Spark Thrift Server
* @param udfFilter UDF names which will be ignored
*/
def udfRegister(spark: SparkSession, udfFilter: Set[String] = Set()): Unit = {
// name,classname,returnType(udf need)
udfConfigArray.filter(!_.startsWith("#")).foreach(record => {
val registerInfoArray = record.split(",")
if (registerInfoArray.size > 1
&& (udfFilter.isEmpty || udfFilter.contains(registerInfoArray(0)))) {
logInfo(s"register udf info begins: ${record}")
try {
if (registerInfoArray.size == 2) {
val Array(udfName, className) = registerInfoArray
val instance = getUDAFInstanceByClass(className)
if (instance != null) spark.sqlContext.udf.register(udfName, instance)
logInfo(s"register udf info done. : ${record}")
} else if (registerInfoArray.size == 3) {
val Array(udfName, className, returnType) = registerInfoArray
var returnDataType: DataType = null
returnType match {
// Numeric types
case "ByteType" => returnDataType = DataTypes.ByteType
case "ShortType" => returnDataType = DataTypes.ShortType
case "IntegerType" => returnDataType = DataTypes.IntegerType
case "LongType" => returnDataType = DataTypes.LongType
case "FloatType" => returnDataType = DataTypes.FloatType
case "DoubleType" => returnDataType = DataTypes.DoubleType
// String types
case "StringType" => returnDataType = DataTypes.StringType
// Binary type
case "BinaryType" => returnDataType = DataTypes.BinaryType
// Boolean type
case "BooleanType" => returnDataType = DataTypes.BooleanType
// Datetime type
case "TimestampType" => returnDataType = DataTypes.TimestampType
case "DateType" => returnDataType = DataTypes.DateType
case "MapType(StringType:IntegerType)" => returnDataType =
DataTypes.createMapType(StringType, IntegerType)
case "ArrayType(StringType)" => returnDataType =
DataTypes.createArrayType(StringType)
case "ArrayType(IntegerType)" => returnDataType =
DataTypes.createArrayType(IntegerType)
case "ArrayType(LongType)" => returnDataType =
DataTypes.createArrayType(LongType)
case "ArrayType(FloatType)" => returnDataType =
DataTypes.createArrayType(FloatType)
case "ArrayType(DoubleType)" => returnDataType =
DataTypes.createArrayType(DoubleType)
case "ArrayType(ByteType)" => returnDataType =
DataTypes.createArrayType(ByteType)
case "ArrayType(BooleanType)" => returnDataType =
DataTypes.createArrayType(BooleanType)
case _ => None
}
spark.sqlContext.udf.registerJava(udfName, className, returnDataType)
logInfo(s"register udf info done. : ${record}")
}
} catch {
case ex: Throwable =>
logInfo(s"record udf register error, " +
s"error info: ${ex.getCause} ...................... ")
ex.printStackTrace()
}
}
})
}
/**
* Register MongoDB tables to Spark Thrift Server according to MongoDB configuration.
* @param spark SparkSession from Spark Thrift Server
*/
def mongoTableRegister(spark: SparkSession): Unit = {
val mongoTableConfigArrayDLLNew = mongoTableConfigArrayDLLProcess(
mongoTableDLLConfigArray.map(_.trim).filter(!_.startsWith("#")))
val billTableDescDLL = mongoTableConfigArrayDLLNew(0).split("->")(2).trim
mongoTableConfigArray.filter(!_.startsWith("#")).foreach(record => {
if (record.split(",").size == 3) {
var arrays = record.split(",")
var (caseClassName, tableName, collectionName) = (arrays(0), arrays(1), arrays(2))
try {
logInfo(s"MongoTable record register begins: ${record}")
logInfo(s"caseClassName: ${caseClassName}, tableName: ${tableName}, " +
s"collectionName: ${collectionName}")
val readConfig = ReadConfig(Map("collection" -> collectionName),
Some(ReadConfig(spark.sparkContext)))
val collection = MongoSpark.load(spark.sparkContext, readConfig)
.toDF(StructType.fromDDL(billTableDescDLL))
collection.createOrReplaceGlobalTempView(tableName)
logInfo(s"MongoTable record register ends: ${record}")
} catch {
case x: Throwable =>
logInfo(s"MongoTable record register error. caseClassName: ${caseClassName}, " +
s"tableName: ${tableName}, collectionName: ${collectionName}")
x.printStackTrace()
logInfo("error info: " + x.getMessage)
}
}
})
mongoTableConfigArrayDLLNew.foreach(tableDesc => {
logInfo(s" MongoTable record register from DLL, tableDesc begins: ${tableDesc}")
try {
val tableDescArray = tableDesc.split("->").map(_.trim)
if (tableDescArray.size == 3 && !tableDescArray.contains("billsplit_record_d")) {
val tableName = tableDescArray(0)
val collectionName = tableDescArray(1)
val tableDescDLL = tableDescArray(2)
val readConfig = ReadConfig(Map("collection" -> collectionName),
Some(ReadConfig(spark.sparkContext)))
val collection = MongoSpark.load(spark.sparkContext, readConfig)
.toDF(StructType.fromDDL(tableDescDLL))
collection.createOrReplaceGlobalTempView(tableName)
logInfo(s" MongoTable record register from DLL success, table: ${tableName}")
}
} catch {
case x: Throwable =>
logInfo(s"register from DLL error. tableDesc: $tableDesc")
x.printStackTrace()
logInfo("error info: " + x.getMessage)
}
logInfo(s" MongoTable record register from DLL, tableDesc ends: $tableDesc")
})
}
/**
* Parse the original configuration lines for MongoDB fields description and return a configuration array
* that contains fields description for each table.
* @param array array which contains original configuration lines for MongoDB fields description
* @return Array[String]
*/
def mongoTableConfigArrayDLLProcess(array: Array[String]): Array[String] = {
val sbf = new StringBuffer()
array.foreach(str => sbf.append(" ").append(str).append(" "))
val tableDescArray = sbf.toString.split("end#")
val tableDescArrayNew = tableDescArray.map(tableDesc => {
if (tableDesc.split("->").size == 3) {
var tableDescArray = tableDesc.split("->").map(_.trim)
val tableName = tableDescArray(0)
val collectionName = tableDescArray(1)
val tableDescDLL = tableDescArray(2)
val columnArray = tableDescDLL.split(",")
val columnArrayProcess = columnArray.filter(_.split(" ").size >= 2).map(column => {
val valueArray = column.trim.split(" ")
valueArray(0) + " " + valueArray(1)
})
if (columnArrayProcess.size != columnArrayProcess.distinct.size) {
logInfo(s"mongoTableConfigArrayDLLProcess !!!!! : config file columnArrayProcess distinct item! ")
columnArrayProcess.map(x => (x, 1)).groupBy(x => x._1).map(x => x._2)
.filter(_.length > 1).foreach(x => logInfo(s"distinct item : ${x.map(_._1).mkString(",")} "))
}
val value = tableName + " -> " + collectionName + " -> " + columnArrayProcess.distinct.mkString(",")
logInfo(s"mongoTableConfigArrayDLLProcess #####: ${value}")
value
} else ""
})
tableDescArrayNew
}
/**
* Return an instance of UserDefinedAggregateFunction according to UDF class name.
* @param className class name of UDF
* @return UserDefinedAggregateFunction
*/
def getUDAFInstanceByClass(className: String): UserDefinedAggregateFunction = {
var instance: UserDefinedAggregateFunction = null
try {
instance = Class.forName(className).newInstance.asInstanceOf[UserDefinedAggregateFunction]
} catch {
case ex: Throwable => {
logInfo(s" instance ${className} error, error info: ${ex.getCause} ...................... ")
ex.printStackTrace()
}
}
instance
}
/**
* Read the configuration file and return an array which contains all lines of the configuration file.
* @param configFile the path which stands for a configuration file.
* @return Array[String]
*/
def getConfigArray(configFile: String): Array[String] = {
val configArray = new ArrayBuffer[String]()
try {
val configPath = configFile
val configFileName = configPath.substring(configPath.lastIndexOf(File.separator) + 1);
logInfo(s"local ${configFileName}, path: ${configPath}")
val source = Source.fromFile(configPath)
val localFiles = source.getLines().toArray
configArray ++= localFiles
logInfo(s"local ${configFileName}, path: ${configPath} done!")
} catch {
case x: Throwable =>
}
configArray.toArray
}
/**
* Return spark conf value according to spark conf key. if sparkConf does not contain confKey,
* then the default will be returned.
* @param sparkConf the instance of SparkConf
* @param confKey a key of sparkConf
* @param default the default value returned if sparkConf does not contain confKey
* @return String
*/
def getSparkConfigValue(sparkConf: SparkConf, confKey: String, default: String = ""): String = {
var value: String = null;
if (sparkConf.contains(confKey)) {
value = sparkConf.get(confKey)
} else {
logWarning("not found config key by " + confKey + ".")
value = default
}
value
}
}
2.4.4 修改HiveThriftServer2.scala
修改sql/hive-thriftserver/src/main/scala/org/apache/spark/sql/hive/thriftserver/HiveThriftServer2.scala
修改的目的是将2.5.3的新增代码集成到Spark Thrift Server中, 修改的代码行集中在main方法当中,这里仅列出main方法的代码。
def main(args: Array[String]) {
Utils.initDaemon(log)
val optionsProcessor = new HiveServer2.ServerOptionsProcessor("HiveThriftServer2")
optionsProcessor.parse(args)
logInfo("Starting SparkContext")
SparkSQLEnv.init()
ShutdownHookManager.addShutdownHook { () =>
SparkSQLEnv.stop()
uiTab.foreach(_.detach())
}
val executionHive = HiveUtils.newClientForExecution(
SparkSQLEnv.sqlContext.sparkContext.conf,
SparkSQLEnv.sqlContext.sessionState.newHadoopConf())
try {
val server = new HiveThriftServer2(SparkSQLEnv.sqlContext)
server.init(executionHive.conf)
server.start()
logInfo("HiveThriftServer2 started")
listener = new HiveThriftServer2Listener(server, SparkSQLEnv.sqlContext.conf)
SparkSQLEnv.sparkContext.addSparkListener(listener)
uiTab = if (SparkSQLEnv.sparkContext.getConf.getBoolean("spark.ui.enabled", true)) {
Some(new ThriftServerTab(SparkSQLEnv.sparkContext))
} else {
None
}
//added by lixinyang on 2022-03-31
//register udf and mongodb tables after spark thrift server started.
logInfo("===== begin to register udf and mongodb tables")
val sparkSession = SparkSQLEnv.sqlContext.sparkSession
UdfLoadUtils.init(sparkSession)
UdfLoadUtils.udfRegister(sparkSession)
UdfLoadUtils.mongoTableRegister(sparkSession)
logInfo("===== end to register udf and mongodb tables")
// If application was killed before HiveThriftServer2 start successfully then SparkSubmit
// process can not exit, so check whether if SparkContext was stopped.
if (SparkSQLEnv.sparkContext.stopped.get()) {
logError("SparkContext has stopped even if HiveServer2 has started, so exit")
System.exit(-1)
}
} catch {
case e: Exception =>
logError("Error starting HiveThriftServer2", e)
System.exit(-1)
}
}
2.4.5 项目编译打包
1). 在项目根目录下执行如下命令,打包项目。
mvn clean
mvn package -X -Denforcer.skip=true -DskipTests -f pom.xml
2). 生成spark二进制安装包(在spark项目根目录下执行如下命令)
find ./ -name *.sh | xargs chmod +x
find ./ -name "mvn" | xargs chmod +x
./dev/make-distribution.sh --name custom-spark --pip --tgz -Pyarn -Phadoop-2.7 -Dhadoop.version=2.7.3 -Phive -Phive-thriftserver -DskipTests
因为spark-2.4.3与hadoop3.X不兼容,只好用hadoop2.7,且因为生成环境的hadoop是3.1版本,所以这里编译生成spark二进制包的时候需要将hadoop依赖也打包进来。另外需要同时指定hive和hive-thriftserver的profile,否则将不会编译Spark Thrift Server. 因为Spark项目很大,编译并生成二进制安装包需要较长的时间,请耐心等待(根据机器配置不同耗时会有所不同,一般情况下会需要1个小时左右)。
上述命令执行完成后,会在spark项目根目录下生成spark-2.4.3-bin-custom-spark.tgz文件,这个就是spark的二进制安装包。后续的安装部署是基于这个包来进行的。
2.5 程序部署
2.5.1 配置文件
1). mongotable.config
#caseClassName,tableName,collectionName
Test,stu_table,stu_collection
2). mongotabledll.config
#tableName,collectionName,fieldsDesc
stu_table -> stu_collection ->
_id String,
range_key Int,
stu_name String,
age Int,
class_no Int
end#
class_table -> class_collection ->
_id String,
range_key Int,
class_no String,
class_name Int
end#
3). udf.config
#functionName,className,ReturnType
mongoupdate,org.example.data.udf.common.udf.MongoRecordListSink,StringType
2.5.2 启动脚本
run9994.sh(这里以提交到Spark Standalone集群为例,如果是提交到Yarn集群,需要将–master改为yarn,且删掉–total-executor-cores,并增加–queue, --deploy-mode, --num-executors 等参数。脚本中的参数仅供参考,发布到生产环境后应该根据实际服务器资源和业务负载做相应调整。)
export HADOOP_CONF_DIR=/usr/hdp/3.1.0.0-78/hadoop/conf/
export SPARK_HOME=/home/spark-thrift-server/spark-2.4.3-bin-custom-spark
cd $SPARK_HOME/home/conf
$SPARK_HOME/sbin/start-thriftserver.sh \
--hiveconf hive.server2.thrift.port=9994 \
--hiveconf hive.server2.thrift.bind.host=0.0.0.0 \
--hiveconf hive.server2.idle.session.timeout=0 \
--hiveconf hive.server2.idle.operation.timeout=-3600000 \
--hiveconf hive.server2.session.check.interval=3000 \
--conf spark.sql.hive.thriftServer.singleSession=true \
--conf spark.sql.codegen=true \
--conf spark.sql.shuffle.partitions=36 \
--conf spark.locality.wait.process=0 \
--conf spark.locality.wait.node=0 \
--conf spark.locality.wait.rack=0 \
--conf spark.hadoop.mapreduce.input.fileinputformat.input.dir.recursive=true \
--conf spark.hive.mapred.supports.subdirectories=true \
--conf spark.mongodb.input.uri="mongodb://dbuser:dbpassword@127.0.0.1:27017/db" \
--conf spark.mongodb.input.collection="stu_collection" \
--conf spark.mongodb.input.partitioner='MongoShardedPartitioner' \
--conf spark.mongodb.input.partitionerOptions.shardkey='range_key' \
--conf spark.udf.config="$SPARK_HOME/home/conf/udf.config" \
--conf spark.mongo.table.config="$SPARK_HOME/home/conf/mongotable.config" \
--conf spark.mongo.tableDLL.config="$SPARK_HOME/home/conf/mongotabledll.config" \
--conf spark.driver.extraJavaOptions="-Dlog4j.configuration=log4j.properties" \
--conf spark.executor.extraJavaOptions="-Dlog4j.configuration=log4j-executor.properties" \
--master spark://master:7077 \
--driver-memory 6g \
--executor-memory 16g \
--executor-cores 40 \
--total-executor-cores 480 \
--jars $SPARK_HOME/jars/dataservice-data-udf.jar,$SPARK_HOME/jars/mongo-java-driver-3.12.5.jar,$SPARK_HOME/jars/mongo-spark-connector_2.11-2.4.3.jar,$SPARK_HOME/jars/mysql-connector-java-8.0.16.jar,$SPARK_HOME/jars/mongo-spark-1.0-shaded.jar
2.5.3 安装部署
1). 上传2.5.5节中生成的spark部署包spark-2.4.3-bin-custom-spark.tgz到CentOS服务器的某个目录下,执行如下命令
tar -zxvf spark-2.4.3-bin-custom-spark.tgz
cd spark-2.4.3-bin-custom-spark
2). 接着上传home.tar.gz, jars.tar.gz到该目录,并解压。分别表示spark thrift server需要用到的配置文件和启动脚本(就是上面2.6.1和2.6.2的配置文件和启动脚本,需要根据实际情况修改)和程序所依赖的一些第三方jar包。
tar -zxvf home.tar.gz
tar -zxvf jars.tar.gz
rm -f home.tar.gz jars.tar.gz
#创建日志目录
mkdir logs
home.tar.gz的内容:
jars.tar.gz的内容:
3). 启动程序
这里的例子是Spark Thrift Server运行在Spark Standalone集群,故需要先启动集群,再启动Spark Thrift Server;如果是运行在Yarn集群中,且Yarn集群是已经启动的话,则只需要启动Spark Thrift Server,并需要相应修改启动脚本run9994.sh.
#启动Spark Standalone集群
cd $SPARK_HOME
./sbin/start-all.sh
#启动Spark Thrift Server
cd $SPARK_HOME/home/bin
./run9994.sh
2.6 程序测试
上面2.6程序部署和启动完成后,我们用DbVisualizer通过JDBC连接到Spark Thrift Server.
2.6.1 查询测试
1). 查询mongodb测试1(耗时1.6s)
select * from global_temp.stu_table limit 10;
2). 查询mongodb测试2(耗时1.2s)
select * from global_temp.class_table
where _id='20210600_18606952';
2.7 开发Rest接口提供SQL查询MongoDB服务
根据上面部署成功的Spark Thrift Server, 为了进一步方便客户使用,可以在此基础上开发Java程序,此程序后台通过JDBC连接到Spark Thrift Server,并对外提供Rest接口服务。客户可以通过调用此Rest接口,将SQL语句提交到Spark Thrift Server,Spark Thrift Server查询到MongoDB的数据后,Java程序将数据通过Rest接口返回给客户。限于篇幅,这里就不再展开论述了。仅提供一个接口测试的截图,供大家参考。