大数据Spark实战第九集 数据获取和呈现 并行架构

86 篇文章 47 订阅

如何获取业务数据库的数据

在前面的内容中,我们特意说过在现实情况中,数据并不会被轻易获取,通常数据都在业务数据库中,需要将其抽取,进行下一步的转换操作。这一步在真实环境中会花费大量时间,尤其是数据量特别大的情况,因为通常会涉及巨量的读写性能消耗。

在不同的业务场景下,业务数据库通常会有不同的选择,主要分为两类:关系型数据库和NoSQL 数据库。

  • 关系数据库:是创建在关系模型基础上的数据库,借助集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。关系型数据库主要使用 SQL 作为自己的查询语言。

  • NOSQL(Not Only SQL):是对不同于传统关系数据库的数据库管理系统的统称。该系统允许部分数据使用 SQL 系统存储,允许其他数据使用 NOSQL 系统存储。其数据存储可以不需要固定的表格模式以及元数据,也经常会避免使用 SQL 的连接操作,一般有分布式、高可用、高可扩展的特征。

本课时的主要内容有:

  • 项目架构

  • 关系型数据库的数据导出

  • NoSQL 数据库的数据导出

项目架构

在开始本课时的主要内容前,我们先来看看整个项目的架构,也让你可以更好地了解这个项目的设计与规划。

下图从下至上是系统的分层设计,最下面是数据源,也就是我们的业务数据库。它通过数据导入层,导入到我们的大数据平台中,这个大数据平台通常由 Hadoop 生态构建,通过数据转换层进行转换与处理,处理后生成的数据集合可以看成数据集市,最后由 BI 应用访问数据集市层进行报表生成。可以看到数据从下往上地单向流动。

1.png

关系型数据库的数据导出

关系型数据库的导出过程比较简单,Spark 也有现成的方案,在之前的课程中,我们学习过 read 读取器 API,可以很方便地从支持 JDBC 的数据库中拉取数据,如下面的代码所示:

import org.apache.spark.sql.SparkSession
object BICubing {
    
  def main(args: Array[String]): Unit = {
      
    val spark = SparkSession.builder().appName("BI-CUBING")
    .master("local")
    .getOrCreate()  
    
    //busniess表
    val busniess = spark.read.format("jdbc")
    .option("driver","com.mysql.jdbc.Driver")
    .option("url", "jdbc:mysql://master:3306/ttable")
    .option("dbtable", "busniess")
    .option("user", "root")
    .option("password", "123456")
    .load()
    
    //user表
    val user = spark.read.format("jdbc")
    .option("driver","com.mysql.jdbc.Driver")
    .option("url", "jdbc:mysql://master:3306/ttable")
    .option("dbtable", "user")
    .option("user", "root")
    .option("password", "123456")
    .load()
    
    //checkin表
    val checkin = spark.read.format("jdbc")
    .option("driver","com.mysql.jdbc.Driver")
    .option("url", "jdbc:mysql://master:3306/ttable")
    .option("dbtable", "chenckin")
    .option("user", "root")
    .option("password", "123456")
    .load()
    
    //tip表
    val tip = spark.read.format("jdbc")
    .option("driver","com.mysql.jdbc.Driver")
    .option("url", "jdbc:mysql://master:3306/ttable")
    .option("dbtable", "tip")
    .option("user", "root")
    .option("password", "123456")
    .load()
    
    //review表
    val review = spark.read.format("jdbc")
    .option("driver","com.mysql.jdbc.Driver")
    .option("url", "jdbc:mysql://master:3306/ttable")
    .option("dbtable", "review")
    .option("user", "root")
    .option("password", "123456")
    .load()
busniess.createOrReplaceTempView(<span class="hljs-string">"busniess"</span>)

    user.createOrReplaceTempView(“user”)
    tip.createOrReplaceTempView(“tip”)
    checkin.createOrReplaceTempView(“checkin”)
    review.createOrReplaceTempView(“review”)    
  }
  
}

从代码中可以看到,读取出来的数据可直接进行转换与处理,这个过程需要比较长的时间,主要取决于数据量的大小与数据库的读性能。由于数据库不是分布式的,它的读性能至关重要。

NoSQL 数据库的数据导出

NoSQL 数据库主要以开源软件为主,产品丰富,使用广泛。在很多场景下,它已经取代了关系型数据库,其中比较有代表性的就是 MongoDB。

MongoDB 是一个文档数据库,它将数据存储在类似 JSON 的文档中。这是认识数据的最自然方法,比传统的行/列模型更具表现力和功能。它的主要特点有:

丰富的 JSON 文档

  • 是处理数据最自然、有效的方式。

  • 支持数组和嵌套对象作为值。

  • 允许灵活和动态的模式。

强大的查询语言

  • 丰富且富有表现力的查询语言,无论你的文档中有多少个嵌套,都可以按任何字段进行过滤和排序。

  • 支持聚合和其他现代用例,例如基于地理的搜索、图形搜索和文本搜索。

  • 查询本身就是 JSON,因此很容易组合,不再需要连接字符串来动态生成 SQL 查询。

类似关系型数据库的一些特性

  • 具有快照隔离的分布式多文档 ACID 事务。

  • 支持查询连接。

从上面可以看出,MongoDB 的底层数据结构和 JSON 非常类似,所以 MongoDB 也提供直接将数据导出为 JSON 格式的工具,我们可以使用 mongoexport 命令将文档集合导出为 json 文件。导出命令如下面的代码所示:

mongoexport -d dbtable -c busniess --json -o /yourpath/busniess.json
mongoexport -d dbtable -c review --json -o /yourpath/review.json 
mongoexport -d dbtable -c tip --json -o /yourpath/tip.json
mongoexport -d dbtable -c user --json -o /yourpath/user.json
mongoexport -d dbtable -c checkin --json -o /yourpath/checkin.json

导出完成后,大多数情况下,还需要将其上传到 Hadoop 的文件系统 HDFS 中,HDFS 提供了 put 命令,非常方便,命令如以下代码所示:

hadoop dfs -put /yourpath/busniess.json /dw/bi/busniess.json
hadoop dfs -put /yourpath/review.json /dw/bi
hadoop dfs -put /yourpath/tip.json /dw/bi/
hadoop dfs -put /yourpath/user.json /dw/bi/
hadoop dfs -put /yourpath/checkin.json /dw/bi/

第一个路径地址是本地文件地址,也就是 MongoDB 导出的地址,第二个路径地址为上传到 HDFS 的文件夹地址。

上传完成后,还需要用 Spark 读取器 API 进行读取,以便后续的转换与处理,代码如下:

import org.apache.spark.sql.SparkSession
object BICubing {
  
  def main(args: Array[String]): Unit = {
    
    val spark = SparkSession.builder().appName("BI-CUBING")
    .master("local")
    .getOrCreate()  
    
    val busniess = spark.read.format("json").load("/dw/bi/busniess.json")
    val user = spark.read.format("json").load("/dw/bi/user.json")
    val checkin = spark.read.format("json").load("/dw/bi/checkin.json")
    val review = spark.read.format("json").load("/dw/bi/review.json")
    val tip = spark.read.format("json").load("/dw/bi/tip.json")
    busniess.createOrReplaceTempView("busniess")
    user.createOrReplaceTempView("user")
    tip.createOrReplaceTempView("tip")
    checkin.createOrReplaceTempView("checkin")
    review.createOrReplaceTempView("review")
      
  }
  
}

总结

数据导出过程非常耗时且重要,是 ETL 的重要组成部分,也是数据分析的基础。通常来说,在生产环境中,你的业务数据库不止有一种,所以这个过程有可能非常复杂。本课时完成的是分层设计中的数据导入层,使用的是数据源层的数据。

另外,大家可以看到,在 NoSQL 数据库的数据导出的过程中,既有命令行,也有 Spark 代码,两者交替进行,所以你还需要将这些过程整合到一个过程中去,这部分我们将在下个课时介绍。

本节课的内容看似简单,但要运用到实际情况中还需反复练习,所以我们暂不介绍更多,希望你在课后努力消化,有问题欢迎留言。


如何构建数据立方体

在前面的课时中,我们已经将数据从业务数据库中导出到大数据平台里,可以简单地将导出后的数据集合视为数据仓库。当然在实际情况中,数据也不可能这么合适,这个时候就需要进行数据清洗与转换,在这之后,数据集合才能被称其为数据仓库,这个过程就是我们前面提到的 ETL。

从数据源到数据集市的整个过程如下图所示。而我们今天要学习的是下一个步骤:为数据仓库中的数据构建数据立方体,也就是图中的 Cubing 操作。生成的数据立方体就是实际意义上的数据集市。

1.png

本课时的主要内容有:

  • 构建数据立方体

  • 整合脚本

  • 调度平台

构建数据立方体

这节课,我们继续用 Yelp 内部业务数据集(Yelp 2016 Dataset Challenge)中的数据进行演示。

我们用其中的 busniess 表与 review 表构建一个数据立方体, 用来分析有关评论的信息,代码如下:

    //接上个课时的代码
    val sqlStr = "SELECT  r.cool, r.funny, r.stars, r.userful, l.city, l.state, r.date FROM  business l JOIN review r ON l.business_id = r.business_id"
    //执行SQL
    spark.sql(sqlStr).write.orc("/yourpath/dm")

用连接操作,将 busniess 表和 review 表连接在一起,并过滤 掉了无关字段,例如 review 表中的 text 字段,该字段是一大段文本,占用空间非常大,过滤掉这个字段可以显著提高查询性能。不过这里要特别说明的是,我们可以用一些人工智能技术将这个字段处理为结构化的数据,从而便于后续处理,这也是 ETL 的一种。其中要强调的是,最后保存到文件系统时,我们采用了 ORC 格式(Optimized Row Columnar,是一种 Hadoop 生态圈中的列式存储格式),这对于数据立方体来说非常友好,ORC 格式可以显著降低连接后的数据集大小。总之,在生成数据立方体的同时,需要考虑如何优化查询性能。

同样,在一个较大型的组织中,一般会同时构建成百上千个数据立方体,于是选择 ORC 格式储存就显得尤为重要。因为在一个正常的商业智能系统的项目中,一般来说 ETL 和构建数据立方体会占整个开发时间的 70%-80%,而真正的分析只需要很少的时间。

根据“第 39 课时|作为 Yelp 运营负责人,如何根据数据进行决策”的内容我们已经知道,该数据立方体的维度主要分为地点(city、state)和时间(date),度量分别为 cool、funny、stars、useful 字段。

整合脚本

数据立方体构建完成后,需要将数据导出与构建数据立方体的过程整合,以便能够一次性完成整个过程。

以业务数据库 MongoDB 为例,我们先把 MongoDB 数据导出并上传到 HDFS 的过程拆开,分别写成两个脚本。

第一个脚本如以下代码所示,是负责将 MongoDB 中的数据导出到本地文件系统的 mongoexport.sh:

#!/bin/bash
mongoexport -d dbtable -c busniess --json -o /yourpath/busniess.json
mongoexport -d dbtable -c review --json -o /yourpath/review.json 
mongoexport -d dbtable -c tip --json -o /yourpath/tip.json
mongoexport -d dbtable -c user --json -o /yourpath/user.json
mongoexport -d dbtable -c checkin --json -o /yourpath/checkin.json

第二个脚本是将导出的文件上传到 HDFS 的脚本 upload.sh,如以下代码所示:

#!/bin/bash
hadoop dfs -put /yourpath/busniess.json /dw/bi/busniess.json
hadoop dfs -put /yourpath/review.json /dw/bi
hadoop dfs -put /yourpath/tip.json /dw/bi/
hadoop dfs -put /yourpath/user.json /dw/bi/
hadoop dfs -put /yourpath/checkin.json /dw/bi/

然后我们需要将 Spark 代码打包为一个 jar 包,并编写一条提交 Spark 作业的命令,如下面的代码所示:

spark-submit --name Cubing --class com.yelp.BICubing --master yarn-cluster --executor-memory 2G --num-executors 10 /yourpath/cubing.jar

经过上述操作,所有的过程就都脚本化了。现在我们需要调度这三个脚本,使它们在一定的时间以一定的顺序执行,这就需要用到调度平台。

调度平台

前面提到过,在一个较大型的组织中,一般会同时构建成百上千个数据立方体,每天定时运行的作业多达数千个,作业与作业之间的依赖错综复杂,并非像我们演示的项目一样,会按照顺序执行。如果没有一个统一的作业调度工具的话,难以管理如此复杂的工作流。

在生产环境的集群中,一般都会配备一个调度器,也称为调度中心,负责集群所有的作业调度工作。Hadoop 生态圈有 Java 编写的 Apache Oozie,但由于其功能的局限性,尤其不能表现有向无环图的作业依赖关系,已经逐渐退出历史舞台。

目前比较成熟的调度器有 Airbnb 的 Airflow 和 LinkedIn 的 Azkaban。本节课采用 Airflow 作为系统的调度中心。

Airflow 是用 Python 实现任务管理、调度、监控工作流的平台。与 Ozzie 的 XML 配置文件相比,Airflow 的理念是“配置即代码”,对于描述工作流、判断触发条件等过程,全部采用 Python 脚本,编写工作流就像在写脚本一样,能更快捷地在线上做功能扩展。Airflow 充分利用 Python 的灵巧轻便,是一款非常好用的调度器。

下面的图片是 Airflow 的主界面,DAG 选项列出了所有工作流的配置,可以看到每份配置都是一份 Python 代码文件。

Drawing 1.png

下图是以可视化的形式展现数据处理的工作流:

Drawing 2.png

我们可以在线编辑配置代码文件,下图是一份代码文件:

Drawing 3.png

Airflow 的作业调度配置文件就是一个 Python 脚本。在脚本中,可以用 Python 灵活地定义计算作业工作流。编写完成后,需要将该脚本放置在位于 Airflow 安装目录下、airflow.cfg 文件中配置项 dags_folder 指定的目录下,放置完成即可生效。根据本课时的内容,一共需要配置 3 个作业:MongoExport、Upload 和 BICubing,配置文件如下:

"""
Code that goes along with the Airflow tutorial located at:
https://github.com/airbnb/airflow/blob/master/airflow/example_dags/tutorial.py
"""
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from datetime import datetime, timedelta
 
## 定义调度参数
default_args = {
    'owner''yourname',
    'depends_on_past'False,
    'start_date': datetime(2018928),
    'email': ['airflow@example.com'],
    'email_on_failure'False,
    'email_on_retry'False,
    'retries'1,
    'retry_delay': timedelta(minutes=5),
    # 'queue': 'bash_queue',
    # 'pool': 'backfill',
    # 'priority_weight': 10,
    # 'end_date': datetime(2020, 1, 1),
}
 
dag = DAG('dag-datalake', default_args=default_args)
 
## 定义3个作业
task1 = BashOperator(
    task_id='MongoExport',
    bash_command='./mongoexport.sh',
    dag=dag
)
 
task2 = BashOperator(
    task_id='Upload',
    bash_command='./upload.sh',
    dag=dag
)
 
task3 = BashOperator(
    task_id='Cubing',
    bash_command='spark-submit --name Cubing --class com.yelp.BICubing --master yarn-cluster --executor-memory 2G --num-executors 10 /yourpath/cubing.jar',
    dag=dag
)
 
## 定义作业执行顺序
task2.set_upstream(task1)
task3.set_upstream(task2)

在脚本里,我们定义了 3 个计算作业及其执行命令,最后定义了作业之间的依赖关系,这会决定作业的执行顺序。定义的方法非常简单,每个作业只需要指定自己的上游作业即可:

task2.set_upstream(task1)
task3.set_upstream(task2)

这样定义的计算作业的工作流如下图所示:

Drawing 4.png

我们还可以用如下代码的方式,构造一个作业依赖多个作业的情况:

task1.set_upstream(task2,task3)

定义的结果如下图所示:

Drawing 5.png

定义完成后,用户可以配置执行时间,作业将会自动运行。

总结

在本课时中,我们学习了如何构建数据立方体,在生成数据立方体的同时,也就成功构建了数据集市。另外,我们还引入了开源调度平台 Airflow,完成了整个过程的调度与定时执行,在生产环境中,调度平台的使用非常必要。从这个课时开始,我们引入了开源软件来满足我们的需求,这在大数据开发中非常常见。在下个课时中,我们将使用 Airbnb 开源的另一个软件来满足分析需求与可视化需求。

本节课仍然不留额外的思考题,希望你反复巩固内容,以便应对更复杂的项目。


如何通过 OLAP 与报表呈现结果

在上个课时里,我们已经成功构建了数据立方体,在结果呈现之前只剩下最后两步:多维分析和结果可视化。在一个商业智能系统中,最后两步是呈现给客户的关键,如果是自己开发,需要大量的工作。在这个课时里,我们将介绍两种开源技术 Superset 和 Presto,以满足实时分析和可视化的需求。其中 Presto 可以用来完成我们对数据立方体进行多维分析的需求,而 Superset 则可以用来将 Presto 返回的结果进行可视化呈现。两者之间本身也能很好地集成,非常适合我们的项目。

Presto

Presto 是一种用于大数据的高性能分布式SQL查询引擎,其架构允许用户查询各种数据源,如 Hadoop、AWS S3、Alluxio、MySQL、Cassandra、Kafka 和 MongoDB。你甚至可以在单个查询中查询来自多个数据源的数据。

Presto 是Apache 许可证下发布的社区驱动的开源软件。它是 Dremel 的实现,与 ORC 配合通常会有非常好的效果。从前面的课时内容可以得知,得益于 Dremel 架构,Presto 是非常好的 OLAP 与 Ad-hoc 查询工具,并且支持 JDBC。

它最初是Facebook为数据分析师设计和开发的,用于在 Apache Hadoop 中的大型数据仓库上运行交互式查询。这意味着,它可以直接对 HDFS 上的数据进行分析,这是一个非常有用的特性。联想到数据立方体,可以发现这是一个逻辑的概念。在大数据概念出现以前,数据立方体通常通过事实表 + 维度表,也就是星型模型的方式呈现(如“第 38 课时 | 数据仓库与商业智能系统架构剖析”中的表格所示)。这种方式的数据冗余性很低,但是每次进行分析都需要连接操作,可以说是用时间换空间。

但是有了列式存储以后,这个问题迎刃而解,虽然数据冗余性很高,但实际占用空间并不大,对于后续的多维分析来说又非常友好,无疑比星型模型更优。另外,Presto 可以直接操作 HDFS 上的数据,也就无需将数据立方体再次导出,少了很多工作量。而传统的数据集市往往基于数据库,这样就需要将数据再次导出到数据库中。

此外,还有一些需要简要了解的内容。

Presto 的架构与使用集群计算(MPP)的传统数据库管理系统非常类似,它可以视为一个协调器节点,与多个工作节点同步工作。客户端提交已解析和计划的 SQL 语句,然后将并行任务安排给工作节点。工作节点一同处理来自数据源的行,并生成返回给客户端的结果。同在每个查询上使用 Hadoop 的MapReduce机制的原始 Apache Hive 执行模型相比,Presto 不会将中间结果写入磁盘,从而明显提高速度。

Presto 是用Java 语言编写的,单个 Presto 查询可以组合来自多个源的数据。Presto 提供数据源的连接器,包括 Alluxio、Hadoop 分布式文件系统、Amazon S3 中的文件、MySQL、PostgreSQL、Microsoft SQL Server、Amazon Redshift、Apache Kudu、Apache Phoenix、Apache Kafka、Apache Cassandra、Apache Accumulo、MongoDB 和 Redis。与其他只支持 Hadoop 特定发行版的工具(如 Cloudera Impala)不同,Presto 可以使用任何风格的 Hadoop,也可以不用 Hadoop。它支持计算和存储的分离,可以在本地和云中部署。

由于 Presto 支持 JDBC,所以在使用上与任何支持 JDBC 的 SQL 工具来说并没有任何不同,这点我们在后面可以看到。

Superset

Superset 是由 Airbnb 开源出来的企业级商业智能工具,目前属于 Apache 孵化器,但是其关注度已经超越了很多 Apache 顶级项目。简单来说,Superset 主要提供以下几方面的功能:

  • 与数据源和 OLAP 工具进行集成。

  • 配置数据立方体。

  • Ad-hoc 查询。

  • 以仪表盘和卡片的形式提供的可视化解决方案。

  • 权限管理。

下面结合本项目对 Superset 进行介绍。

首先登录 Superset 应用:

Drawing 0.png

登录后进入仪表盘页面,如下图:

Drawing 1.png

可以看到这里还没有一个存在的仪表盘,我们先不进行创建,而是进入配置数据源的页面,点击 Source 选项卡,选择 Database,并新增一条记录,如下图:

Drawing 2.png
Drawing 3.png

此时,通过 Presto 的 JDBC 就能非常容易地将 Presto 与 Superset 进行集成,在红色方框内填入 JDBC URL,如下图:

Drawing 4.png

配置完成数据源后,我们就可以进入到 SQL Lab 对数据进行查询了,如下图:

Drawing 5.png

在 SQL Lab 中,我们可以对数据源进行探索,也就是前面所说的 Ad-hoc 查询,如下图:

Drawing 6.png

在对 Superset 有了一个大致了解后,下面学习 Superset 最重要的功能:仪表盘(Dashboard)和卡片(Chart)

仪表盘和卡片

仪表盘是商业智能系统面向用户的最终产物,是用户查看一组报表分析结果的地方,如下图:

Drawing 7.png

上图的仪表盘里有 4 个报表,每个报表是一个卡片。卡片与仪表盘是灵活的多对多关系,可以对不同的仪表盘进行复用,非常方便。

我们先创建好一个仪表盘,即来到 Dashboard 页面,新建一个 Dashboard,如下图:

Drawing 8.png

接着生成卡片,从 SQL Lab 的 Explore 按钮开始:

Drawing 9.png

生成过程也就是 Superset 多维分析的实现,所以,这里需要特别注意的是,在点击 Explore 之前,文本框中 SQL 查询的结果(不需要执行)代表数据立方体。也就是说,不管你是否预先构建好了数据立方体,这里都需要用 SQL 进行描述。对于本课程中的项目来说,当然非常简单,只需要一个普通的 SELECT 查询即可,如果没有预先构建立方体,这里可能就需要进行连接操作。

点击 Explore 后,会进入多维分析的配置页面,如下图:

Drawing 10.png

在红色方框内,我们可以按照前面介绍的多维分析方法选择要分析的维度、统计的指标,例如求和、求均值、求最值等。配置完成后,系统会自动匹配可视化方案,如果没有时间序列,一般用饼图,如果有时间序列,则用柱状图或者折线图。点击Save 按钮,就可以将其保存为一个卡片,在保存的时候,可以选择与 MyYelp 仪表盘进行关联。我们可以按照这种方式,生成卡片并将其以仪表盘的形式呈现给用户,自此,整个商业智能系统就算完成了。

总结

本课时主要介绍了两个开源工具 Presto 与 Superset,这两个工具对整个系统的完成度提升来说,效果很大,但如果自己要实现 Superset 这种 BI 工具,可想而知工作量是巨大的。另外, Superset 除了支持常规的 JDBC 的 SQL 工具外,还支持很多开源的分析工具,如 Kylin、Druid 等。最后要说明的是,本课时选用 Presto,并不是因为其查询性能快,而是因为它可以直接操作 HDFS 上的数据。


两个简化了的重要问题:数据更新和数据实时性

在前面的内容中,我们完成了一个商业智能系统,为了方便理解,我做了简化。但是无论从业务复杂性还是技术复杂性来说,我们在课程中模拟的系统与真实环境还有很大的差距。所以这节课,我选取了两个在真实环境中比较常见的问题:数据更新和数据实时性,进行更具体的讨论。

其中,我会围绕数据更新展开详细介绍,以便帮助你根据自己的业务需求进行调整,然后对数据实时性进行简要介绍,帮助你理解真实环境中的复杂数据变化。

本课时的主要内容有:

  • 数据更新

  • 数据实时性

数据更新

通常来说,在一个大数据项目中,如果 ETL(Extract-Transform-Load,抽取、转换和加载,通常也叫数据清洗)所花费的时间占整个项目开发时间的 60% 以下,那么项目失败的风险会比较大。这说明数据清洗在整个项目中的重要性,也指出在实际情况中,数据清洗的工作量通常也非常大。

数据清洗的需求主要和具体业务相关,但是有个问题是每个项目都会面对的,就是如何处理数据更新环节。数据更新是每个数据仓库或商业系统项目都会考虑的问题,这是因为我们的数据源,也就是业务数据库每天都会出现增删改查的操作,如果将这些修改操作按照我们的要求与数据仓库进行同步,会非常麻烦。另外,大数据出现以后,对数据更新的频率也提出了更高的要求,以前一个月同步一次更新还可以接受,现在即使要求每天同步更新,也非常正常。

基于定期同步的需要,便引入了全量数据增量数据的概念,这里我们假设同步周期为 t 来进行分析。

全量数据就是迄今为止存在于数据仓库中的数据,这部分数据是在前面课时中,通过我们开发的转换任务脚本转换而成,但是这些任务只会在系统上线时执行一次,执行完成后,全量数据的处理工作就已经完成了。所以在执行完成后,后面会以 t为周期对更新数据进行处理,这样才能保证系统的结果是最新的。

增量数据和全量数据不同的是,它以日志的形式进行推送,无法直接进行分析,而全量数据虽然可以直接进行分析,却无法捕捉到最新变化。所以在对更新数据进行处理时,比较推荐的解决办法是将增量数据与全量数据进行合并。

合并的方式如下图所示,其中 n 是天数的序号。

Lark20200908-183426.png

也就是说,当前的全量数据和增量数据会生成一份新的全量数据,周而复始。新的全量数据既可以直接进行分析又包含了最新的信息。数据仓库中的每一张表,每天(或者是每个周期)都需要执行这个过程。

而在实际情况中,我们需要根据具体的业务需求进行合并。例如针对订单数据库的情况,每一条订单数据都会存在多个状态,如创建、支付、发货、收货等,如果只保存最新的状态,会漏掉很多有用的信息。在这种情况下,我们在合并时,需要注意保存每个订单的每个状态,用这种方式保存的表称为拉链表,即它反映了表中每个实体的变化过程。而如果需要反映每条记录的修改,就像增量数据中的日志那样,那么保存的表就称为流水表。

以 t 为一天为例,根据合并过程可以看出,在每个合并周期,即每一天后,都会生成一个新版本的全量数据。我们可以采取分区表的方式来保存这些新版本的全量数据,即将每一个新的全量数据称为整个分区表中的一个分区。

这只是一种更新方式,你在面对自己的业务需求时,需要根据具体情况进行调整。业务数据库通常会由于历史原因出现各种各样的问题,这些都会在数据同步与更新的时候暴露出来,需要特别引起重视。

数据实时性

数据实时性的问题与数据更新息息相关,对于商业智能系统的使用方来说,会倾向于要求数据更新的频率越快越好,最好能接近于实时。一旦数据更新同步接近于实时,后续的数据处理当然也需要匹配到相应速度,否则意义不大,而且最终给用户呈现的报表也需要实时体现最新的数据。我们目前的方法做不到这个效果,而如果要数据分析结果体现实时数据,则需要采用一种新的架构,这也是我们下节课的内容。

此外,你也可以结合“第 21 课时|统一批处理与流处理:Dataflow”中的 Dataflow进行理解。如果需要得到最好的实时性,那么势必要对结果的正确性有所舍弃,而如果我们希望用户看到的结果在大多数情况下是正确的,就需要忍受相应的延迟。

所以在某种程度上,数据更新和数据实时性是一个问题。

总结

本课时主要讨论了两个生产环境的需求与场景,在真实的工作环境中,需要一个个地解决问题,经验就会慢慢积累,也要多熟悉开源技术,这样会让你事倍功半。

下节课我们要学习的是最后一个课时“另一种并行:Lambda 架构和 Kappa 架构”。完成下节课的学习后,我们的最后一个模块:商业智能系统实战就完成了。下个课时也可以看成是对这个课时提出的两个问题的解答,学习完下个课时,你将会对大数据处理架构有更加完整、清晰的认识。

在这里,我还想再次强调的是,整体实战模块的内容看上去相对简单,但需要你在课后练习的部分较多。随着我们的课程接近尾声,我也更加想要听到你在实践过程中遇到的问题,你的每一个反馈,对我而言都是最重要的。所以还是欢迎你来留言,与我一起讨论学习 Spark 课程遇到的问题。


另一种并行:Lambda 架构与 Kappa 架构

在上个课时中,我提出了一个问题:数据仓库如何实时获取更新的数据,并将结果融合到最新的报表中,也就是提供统一的查询方式呢?本课时我会介绍一种新的架构模式 Lambda,它能很好地解决上一课时的问题。

本课时的主要内容有:

  • Lambda 架构

  • Kappa 架构

Lambda 架构

在前面的项目中,我们对全量数据进行了一次性处理,也就是批处理。但在真实环境下,数据经常实时更新,一般来说会想到运用流处理技术。但事实上,这两种技术都不能完全满足我们的需求。

流处理通常应对的是低吞吐、低延迟的场景,而批处理应对的则是高吞吐、高延迟的场景。对于 Hadoop 架构下的数据仓库来说,虽然能够对全量数据进行批量处理,但处理完的数据并不能真实反映外部系统的情况,会有一定时延。而流处理平台虽然能对当前时间的窗口数据进行很好的处理,但得到的结果只能反映当前视图。

那是否有一种架构能够将这两种数据处理方式的优点相结合,以达到我们的要求呢?答案就是下面介绍的 Lambda 架构。

Lambda 架构是由 Storm 的创始人 Nathan Marz 提出的一种实时大数据系统方法论,它借鉴了很多函数式编程思想。在 Lambda 架构中,首先将整个系统分为 3 层,即批处理层(Batch Layer)、速度层(Speed Layer)与服务层(Serving Layer),如下图所示。其中,服务层分别依赖速度层与批处理层,而批处理层与速度层之间没有依赖。

Lark20200910-190544.png

批处理层

Lambda 架构的批处理层会对数据进行一次性处理。从数学层面来说,会通过下面的等式来对所有数据进行分析:

query = function(all data)

但等式若要成立却很困难,很难有一种技术可以直接对全量数据进行处理并马上返回结果,那么退而求其次的方案是:基于数据仓库的思路对全量数据进行批处理,然后对批处理的结果视图进行查询,也就是说新的等式应该如下所示。

batch view = function(all data)
query = function(batch view)

这么说或许有些抽象,但用传统数据仓库与数据集市的星形模型可以很好地解释,如下图所示。

Lark20200910-190009.png

星形模型的维表与事实表就是我们所说的批处理视图(在项目中对应我们生成的 ORC 格式的数据立方体),而处理过程可以视作某种函数。在进行查询的时候,无须扫描数据仓库中的所有数据,只需对星形模型所代表的数据方体进行上卷、下钻、切片等操作。比如,如果需要查询某个商品在某个时间段,以及某个地区的销量,只需要在数据集市中用按照时间维度、地区维度以及商品维度进行聚合即可。

但是我们要看到这种方式的缺陷:以这种方式进行处理的时候,在这段时间内收集到的数据不会被包含到批处理视图中,所以查询结果的时效性会很差。此外,数据仓库中的数据被称为主数据集,它表示到目前为止所有可用的数据。而到现在为止,批处理的工作就是基于主数据集生成批处理视图,从而忽略了上述时间内收集到的数据。

服务层

既然批处理的工作只是生成批处理视图,那么我们还需要将其加载并提供查询服务,这就是服务层的工作。服务层是一个数据库,它会保存批处理视图,当新一批次的批处理完成后,新的批处理视图会替换旧的批处理视图,于是新的批处理视图已经包含了新数据的处理结果。

服务层的数据库面对的场景很特殊,在该场景下,只有读查询而完全没有随机写操作,但包含批量写入(在加载新的批处理视图时的一次性操作)。这样一来,服务层的数据库就避免了随机写所带来的各种问题,如一致性等。一般来说,要想保证整个架构的良好扩展性,每个组件都需要有很好的扩展性,所以服务层数据库一般选择分布式数据库,如 HBase。

速度层

当批处理层计算出批处理视图并加载到服务层后,这段时间产生的新数据是没有体现在查询结果中的。于是就需要速度层来对这部分数据进行流处理。

也就是说,速度层负责处理两次批处理之间产生的新数据,但是流处理的模式与批处理不同,它并不是只做一次计算,而是持续不断地处理。所以速度层做的是增量计算,与服务层的批处理视图没有随机写入不同,速度层的流处理视图包含大量随机写入操作,并混合着部分的读操作。

流处理的工作就是处理新数据并更新至实时视图。在下一次批处理完成后,这个间隔产生的实时视图就可以完全丢弃,因为最新的批处理视图已经包含了这部分的结果,而速度层则又从零开始构建实时视图,如此循环往复。从使用场景上来看,实时视图的数据库比批处理视图的数据库的复杂程度要高很多。

我们也可以用下面的等式来概括速度层:

realtime view = function(realtime view,new data)

从等式中可以看出,新的实时视图需要老实时视图作为输入,这就是平时常说的增量计算,也是速度层很大一部分读操作的来源。

Lambda 架构

通过前面三小节的内容,我们已经了解了批处理层、服务层与速度层。可以用如下的数学公式来总结整个操作过程:

batch view = function(all data)
realtime view = function(realtime view,new data)
query = function(batch view, realtime view)

这也解答了本节开头的一个问题:如何实时地对全量数据进行分析?答案是,需要对批处理视图和实时视图进行查询并合并,才能得到正确的、实时的结果。这也是Lambda 架构的操作流程,如下图所示。

Lark20200910-190005.png

这个架构有个优点,一旦批处理层重算生成了新的批处理视图,当前实时视图的结果立刻可以丢弃。也就是说,如果当前的实时视图有什么问题的话,只需要丢弃掉当前的实时视图,并在几小时内(甚至更短的时间),整个系统就可以重新回到正常状态。

整个系统的结果的正确性建立在算法能很好地支持增量计算的基础上。如果算法无法支持增量计算,那么速度层只能采取近似处理,而由于批处理层可以实现精确算法,整个系统仍然具有最终准确性。可以说,Lambda 架构在性能和准确性上做出了很好的权衡,每隔一段时间,批处理层会纠正速度层的错误,速度层的不准确只是暂时的,但这个特性可以给你极大的灵活性。

Nathan Marz 创造 Lambda 架构这个术语来描述一种可扩展、容错数据处理的通用模式,在这个模式中,最重要的是速度层与批处理层。有趣的是,虽然这个架构为什么被命名为 Lambda(λ)我们并不清楚,但从λ 的形状也可以得到解释。λ 的形状似乎暗示了批处理层与速度层通过服务层进行合并的过程,如下图所示,这多少与 Lambda 架构本身有一些契合。从某种层面上来说,Lambda 架构也体现了一种并行计算的思路。

Drawing 3.png

Lambda 架构的原则

Nathan Marz 总结了 Lambda 架构的原则,主要有 3 条。

  • 容错性:Lambda 架构的每个组件都应该具有很好的容错性,在任何情况下,都不会存在数据丢失的情况,此外,对于那些数据处理的错误,也要能很好地恢复。

  • 数据不可变:这一点其实是 Lambda 的灵魂。Lambda 架构之所有能够如上述般架构,最重要的原因是数据不可变,这样的系统具有很小的复杂性,也更易于管理。它只允许查询和插入数据,不允许删除与更改数据。

  • 重算:重算可以使架构充满灵活性,由于主数据集总是可用的,因此总可以重新定义计算来满足新需求,这意味着主数据集并没有和什么模式绑定在一起。

Kappa 架构

前面已经介绍了 Lambda 架构的理论和实现,它的优点有容错性、可扩展性,低延迟读取等,但它的最大缺点在于需要维护两套业务逻辑相同的代码。而当时还是 LinkedIn 的高级技术主管 Jay Kreps 提出了一种类 Lambda 架构的新架构 Kappa 架构,可以改善 Lambda 架构的不足。

Kappa 架构与 Lambda 架构很相似,但它的批处理层被删除,只保留速度层。这样做主要是想避免不得不从头计算一个批处理视图的过程,而是尝试将几乎所有的计算都放在速度层。通过这样的设计,它避免了 Lambda 架构的最大问题:必须对同样的业务需求进行两次实现(批处理层与速度层)。

下图是Kappa 和 Lambda 架构的并排比较。你可以清楚地看到,在 Kappa 架构中唯一缺少的部分是批处理层。

Drawing 4.png

与 Lambda 架构相比,Kappa 架构只会在必要时才对历史数据进行重复计算,而不会定时计算,所有计算都由流计算引擎完成,所以代码只用维护一份。平时数据通常保存在消息队列中,计算的时候从中读取即可,需要一天就读一天,需要全量就读全量,读取后利用流处理系统进行处理,然后输出到服务层,新旧版本输出的结果互不相关,如下图所示。

Lark20200910-185956.png

目前 Kappa 架构最大的缺点在于:由于只使用流处理系统作为计算引擎,所以它的数据处理能力有限,无法对全量的历史数据进行很好的分析。

那么在实际情况中,该如何对Lambda 架构和 Kappa 架构进行选择呢?在下面的表格中,我详细对比了两种架构的数据处理能力、机器开销等,你可以根据需求进行选择。

Lark20200910-190002.png

总结

总体而言,Lambda 架构是在批处理、流处理技术共同作用下的实时大数据架构,它在某种程度上解决了我们上一课时的问题:如何实时获取数据进行处理,并提供统一的查询方式。值得一提的是搜索引擎就是典型的 Lambda 架构,很多开源大数据分析平台也采用了 Lambda 架构,如 Druid 等。而这一课时介绍的两种常见的实时大数据架构,也算是对整个实践模块的升华。

到目前为止,所有的课程就结束了。但别急着走,我还会更新一个彩蛋:“如何成为 Spark Contributor”,向你介绍如何向Spark贡献代码。此外还会在结束语中介绍一些业界比较新颖的架构和理念,也是对整个课程的总结。

最后,我邀请你参与对本专栏的评价,你的每一个观点对我们来说都是最重要的。点击链接,即可参与评价,还有机会获得惊喜奖品!


统一的编程模型,统一的编程语言,统一的架构

在前面 44 课时的内容中,我们学习了 Spark 的计算模型、编程接口和架构。在本课程的最后,我将从这几个方面对本课程进行总结并展望,帮你巩固之前学的内容,如果能引起你的深入思考就更好了。

本课时的主要内容为:

  • 统一的计算模型

  • 统一的编程接口

  • 统一的架构

统一的计算模型

就计算模型而言,“统一”有两层意思:第一层是计算模型统一了批处理与流处理的编程接口,第二层是统一了不同的计算引擎。第一个“统一”我们在“第 21 课时|统一批处理与流处理:Dataflow”中已经学到了,你可以通过对以下几个维度对数据流进行解构:

  • 计算什么(What)。

  • 根据事件时间,哪些数据会参与计算(Where)。

  • 什么时候触发计算(When)。

  • 早期的计算结果如何被修正(How)。

这样便可以统一地满足不同计算场景的需求。

第二个“统一”也很好理解,目前主流的大数据处理引擎均选用 Dataflow 模型作为自己的设计蓝本,这造成的影响是,在未来的一段时间内,Spark 与 Flink 将会变得越来越像(即使目前两者还有差距)。从某种层面上来说,Dataflow 是计算模型的计算模型。

统一的编程接口

虽然说 Flink 与 Spark 均采用了 Dataflow 思想,但两者的编程接口差异还是比较大。那有没有一种技术能够统一不同计算引擎的编程接口呢?答案是肯定的。

Google 在 2016 年宣布其内部项目 Beam 进入 Apache 孵化器。2017 年 1 月,Apache 基金会宣布 Beam 成功毕业,成为 Apache 顶级项目。Apache Beam 的理论基础脱胎于 Google Dataflow 的论文The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing,但不一样的是,Google Beam 对用户暴露 Dataflow 模型(Beam 模型)的编程接口,并兼容多种语言(Java、Python、Go 等),其下层可以适配多个计算引擎。也就是说,Beam 是一套统一的编程模型,它可以在任何引擎上运行批处理和流处理作业,对应的开源实现是一个 SDK(Software Development Kit,软件开发工具包),如下图所示。

Lark20200917-174159.png

目前,Beam 支持的计算引擎有 Apex、Flink、Spark、Google Dataflow(这里的 Google Dataflow 指的是 Google 自己实现 Dataflow 模型的计算引擎,可以在 Google Cloud 使用)、Gearpump 和 samza 等,如下图所示。

Drawing 1.png

虽然 Dataflow 提出了一种统一流处理和批处理的编程接口,但是实现它的计算引擎却各有不同,比如 Spark 或 Flink,这也使得如果要切换计算框架,全部代码都必须重写。而 Beam 统一了大数据处理的计算框架的编程接口,从某种层面上来说,Beam API 是编程接口的编程接口。

Google 开源 Beam 对用户和 Beam 本身来说都是好事,用户的代码更简洁、更具移植性,Beam 社区也会越来越活跃。但 Google 并不是慈善组织,对于 Beam 来说,在功能支持方面,最好的当然是 Google 自己的 Dataflow 计算引擎,随着用户代码越来越具备可移植性,用户也越来越容易且更愿意将数据处理应用部署到 Google Cloud 上,这也是 Google 开源 Beam 的一个目的:希望用 Beam 为 Google Cloud 抢占更多的云计算市场份额。

统一的架构

在架构设计中,我们通常采用分层设计的方式进行规划,一般将大数据平台分为三层:计算层,资源管理与调度层,以及存储层,如下图所示。

在我们熟悉的 Hadoop 中,这三层通常为:

Lark20200917-174208.png

结合前面的内容,可以发现 Beam 统一了计算层,对于用户来说,无论是什么计算引擎,只要使用 Beam API,就没什么不同。从某种层面上来说,Beam API 是大数据编程接口的编程接口。

而资源管理与调度系统就比较有趣了。YARN 虽然比较常用,但它有个缺点,目前只能调度大数据计算作业,不能调度 Web 服务和数据库服务之类的计算需求。也就是说,在资源层面,YARN 并没有做到统一,而目前最火的资源管理与调度系统 Kubernets(简称 K8s)则能很好地调度这些资源。K8s 是 Google 10 多年大规模容器管理技术 Borg 的开源版本。在 Spark 2.4.4 中,Spark 添加了 Spark on Kubernets 的支持,如下图:

Drawing 3.png

这样一来,在资源管理与调度层面,理论上,我们就可以通过 K8s 调度任意类型的计算作业。注意,我在这里强调了“理论上”,也就是说在实际中并不一定有必要这样做。另外,就算 Spark 不支持 Spark on K8s,我们也可以在 K8s 中部署 YARN 来达到目的。从某种层面上来说,K8s 可以看成是资源管理与调度系统的资源管理与调度系统。

最后再来看看存储层。存储层通常为 HDFS,这也很好理解,HDFS 是 Hadoop 的标配。试想一个情况,如果在数据中心里有两个 Hadoop 集群,还有一些其他文件系统,如 Ceph 等,那么有没有办法在文件系统,也就是存储层进行统一呢?答案是有的,我们先来看一种新的技术——Alluxio。

Alluxio 的前身是 Tachyon,同样由伯克利 AMP 实验室开源,是一个提供内存级别读写速度的虚拟分布式存储,是 BDAS 的重要组成部分。它已经在业界大规模使用,如 Palantir、巴克莱银行、百度、阿里云、携程等。组成 Alluxio 的文件系统有三层,如下图所示。

Lark20200917-174228.png

最下面一层是底层文件系统(UFS),中间层是由从节点本地的存储资源组成的分层缓存层(Tired Cache),最上面是 Alluxio 角色节点(Alluxio Cluster)。对于一个 Alluxio 来说,底层文件系统可以是 HDFS、Amazon S3、Ceph 等,而分层缓存层则是作为 Alluxio 这个文件系统的高速副本缓存存在,用户可以按照访问速度依次设置为内存、固态硬盘和机械硬盘。Alluxio 客户端请求 UFS 中的某个目录的文件时,发现这份数据在分层缓存层中有副本,则直接从缓存将数据返回。所以,从这个角度上来说,Alluxio 没有自己的文件系统,它只是“借用”其他文件系统并进行统一封装,这也是其定义中“虚拟”的意义所在。除此之外,“虚拟”还指的是 Alluxio 最重要的一个特性:统一命名空间。

Alluxio 统一命名空间主要是由 Alluxio 提供的挂载 API 实现的,有了命名空间,才能通过 Alluxio 真正地访问多种数据源。默认情况下,Alluxio 的命名空间是挂载到由 alluxio.underfs.address 配置指定的目录下,该目录将作为 Alluxio 的主存储。此外,用户可以使用挂载 API 将其他存储系统的路径挂载到 Alluxio 的目录下,如下图所示。

Lark20200917-174231.png

如此,Alluxio 就融合了不同介质的数据源,并提供了统一访问的 URL:alluxio://host:port/…。从这一个角度上说,Alluxio 是一个整合了多种不同存储介质的分层存储,我们可以将数据按照用途存到 Alluxio 中的不同目录(介质),以达到读写的最佳效率。

看到这里,你应该能够想到,通过 Alluxio 的挂载功能,你可以将任意的文件系统挂载到 Alluxio 的底层文件系统下,从某种层面上来说,Alluxio 是文件系统的文件系统

总结

在了解了 Dataflow、Beam、K8s 以及 Alluxio 这些技术后不难发现,统一的主旋律贯穿始终:Dataflow 是对批处理和流处理的统一,Apache Beam 是对计算框架(大数据编程)的统一,K8s 是对任意计算类型资源的统一,Allxuio 是对任意文件系统的统一。我们可以进一步想象,对于整个数据中心来说,是不是可以用 K8s 和 Alluxio 作为一个统一的大数据操作系统对整个数据中心进行抽象,这样一来,整个数据中心不就可以看成是一台普通服务器了吗?

借用《三个火枪手》中的一句话作为本课时与整个 Spark 课程的结束:

“all for one,one for all。”

人人为我,我为人人。

相信本课程一定能帮助你在 Spark 领域有所突破。最后,我还是邀请你参与对本专栏的评价,你的每一个观点对我们来说都是最重要的。点击链接,即可参与评价,还有机会获得惊喜奖品!


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

办公模板库 素材蛙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值