基于MongoDB的SQL数据服务 原创 精华

Shineyang17
发布于 2022-6-2 09:22
浏览
4收藏

1. 业务背景

某省高速集团为了更好的分析交易流水数据,掌控各个阶段的流水数据的及时性和准确性,新上了对账系统。通过对账系统可以很方便地对交易流水进行统计和查找。例如分析流水是否已出站,交易是否已上传部中心,交易是否在银行已记账,银行是否已划账,部中心是否已拆分,省中心是否已下载拆分数据,省中心是否已清分,省中心是否已拆账,流水是否已上传发票等等。

分析对账系统牵涉的数据,主要特性如下:

  1. 数据种类多。目前数据源多,接近20种,以后可能还会扩展。

  2. 数据量大。每种数据源,记录都不小,其中光拆分数据每月记录接近4000万,存储接近200G。

  3. 性能。各种分析统计性能要求高,实时统计。

  4. 大字段。多个数据源存在大字段,JSON串。如拆分记录中存在多个大字段,计费金额组合、路段编号组合、收费单元编号组合、业主编号组合、收费单元交易金额组合、收费单元时间组合等等都是按固定格式形成的序列数据,长度不固定。在使用中需要解析成具体数据,然后再统计分析。

  5. 模型复杂。多个数据源的关联复杂,不同数据源的分区分表规则各异。

  6. 数据的安全性。对账系统的数据源基本都是核心数据,在安全性,冗余性,一致性都有严格要求。

针对数据特性,项目组初步提出了三种方案:

  1. Hive。Hive是基于Hadoop的一个数据仓库工具,可以用来对数据提取、转化、加载、存储、查询和分析。
    • 优点:
      • 类SQL语法,提供快速开发的能力(简单、容易上手)。
      • Hive的扩展性与集群的扩展性相同,通过集群的扩展获得性能的提升。
      • 支持用户自定义函数,编写符合自己业务需求的函数。
    • 缺点:
      • 执行开销较大,任务运行时间较长,延时较高。
      • 事务限制多,单行记录的频繁更新性能差。
      • 调优比较困难,粒度较粗。
      • Hive部署复杂,运营成本高。
  2. MySQL。MySQL是一个关系型数据库管理系统**,**最流行的关系型数据库管理系统之一。
    • 优点:

      • 易用性。使用最流行、最广泛的查询语言SQL,开发门槛低。
      • 事务简单,使用方便,性能好。
      • 部署简单,运营成本低。
    • 缺点:

      • 强数据模式,强制要求预先确定好数据表的列。模型变更约束多,修改表结构对业务影响大,大数据量情况下变更模型时间长。

      • 性能瓶颈。大数据情况下的汇总、统计性能一般。拆分系统的统计一般都要10个小时以上,严重影响效率。

  3. MongoDB。MongoDB是一个基于分布式文件存储的数据库。
  • 优点:

    • 模式自由。在MongoDB 中存储的数据是无模式的文档,无需事先确定数据的文档属性,通过删除或修改集合中的文档属性,以实现高度灵活性。
    • 性能高。分布式架构在处理海量数据时优势明显。
    • 多副本。数据多副本提高数据的安全性。
    • 分片模式。通过横向扩容的方式,为用户提供了近乎无限的空间。
  • 缺点:

    • 易用性不高。MongoDB使用的是非结构化的查询语言,对开发人员的亲和度不强。

    • 运营、监控、维护成本较高。没有MySQL那样成熟的维护工具。

根据系统的实际情况,讨论决定采用MongoDB来存储数据,主要原因如下:

  1. 性能。对账系统需要支持实时查询和统计,延时要求低。Hive和MySQL在大数据情况下经测试查询时间长,没法达到要求。MongoDB经过测试,性能可以满足要求,基本上响应时间都在秒级。
  2. 模型扩展性。目前对账系统已经有接近20种数据源,以后还会新增数据源。Hive、MySQL中每种数据源都有独立的模型,模型之间通过主外键关联,新增数据源,模型变更复杂。MongoDB中把数据源作为文档的一个属性存储,避免了关联。
  3. 关联查询。对账系统的查询和统计牵涉到多种数据源的关联。在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启动流程:

基于MongoDB的SQL数据服务-鸿蒙开发者社区

JDBC客户端查询MongoDB交互流程:

基于MongoDB的SQL数据服务-鸿蒙开发者社区

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(默认是不编译的)。

基于MongoDB的SQL数据服务-鸿蒙开发者社区

2). 修改sql/hive-thriftserver/pom.xml,该模块因为需要二次开发增加查询MongoDB的功能,故需要引入MongoDB Spark Connector的依赖。

基于MongoDB的SQL数据服务-鸿蒙开发者社区

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的内容:

基于MongoDB的SQL数据服务-鸿蒙开发者社区

基于MongoDB的SQL数据服务-鸿蒙开发者社区

jars.tar.gz的内容:

基于MongoDB的SQL数据服务-鸿蒙开发者社区

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.

基于MongoDB的SQL数据服务-鸿蒙开发者社区

2.6.1 查询测试

1). 查询mongodb测试1(耗时1.6s)

select * from global_temp.stu_table limit 10;

基于MongoDB的SQL数据服务-鸿蒙开发者社区

2). 查询mongodb测试2(耗时1.2s)

select * from global_temp.class_table 
where _id='20210600_18606952';

基于MongoDB的SQL数据服务-鸿蒙开发者社区

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接口返回给客户。限于篇幅,这里就不再展开论述了。仅提供一个接口测试的截图,供大家参考。

基于MongoDB的SQL数据服务-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-6-2 09:22:38修改
5
收藏 4
回复
举报
回复
    相关推荐