Hadoop

概念

就是一个大数据解决方案。它提供了一套分布式系统基础架构。 核心内容包含 hdfs 和 mapreduce。hadoop2.0 以后引入 yarn.

hdfs 是提供数据存储的,mapreduce 是方便数据计算的。

  1. hdfs 又对应 namenode 和 datanode. namenode 负责保存元数据的基本信息, datanode 直接存放数据本身;
  2. mapreduce 对应 jobtracker 和 tasktracker. jobtracker 负责分发任务,tasktracker 负责执行具体任务;
  3. 对应到 master/slave 架构,namenode 和 jobtracker 就应该对应到 master, datanode 和 tasktracker 就应该对应到 slave.

HDFS

Client

Client(代表用 户) 通过与 NameNode 和 DataNode 交互访问 HDFS 中 的文件。 Client 提供了一个类似 POSIX 的文件系统接口供用户调用。

NameNode

整个 Hadoop 集群中只有一个 NameNode。 它是整个系统的“ 总管”, 负责管理 HDFS 的目录树和相关的文件元数据信息。 这些信息是以“ fsimage”( HDFS 元数据镜像文件)和 “ editlog”(HDFS 文件改动日志)两个文件形式存放在本地磁盘,当 HDFS 重启时重新构造出来的。此外, NameNode 还负责监控各个 DataNode 的健康状态, 一旦发现某个 DataNode 宕掉,则将该 DataNode 移出 HDFS 并重新备份其上面的数据。

Secondary NameNode

Secondary NameNode 最重要的任务并不是为 NameNode 元数据进行热备份, 而是定期合并 fsimage 和 edits 日志, 并传输给 NameNode。 这里需要注意的是,为了减小 NameNode 压力, NameNode 自己并不会合并 fsimage 和 edits, 并将文件存储到磁盘上, 而是交由

Secondary NameNode 完成。

DataNode

一般而言, 每个 Slave 节点上安装一个 DataNode, 它负责实际的数据存储, 并将数据信息定期汇报给 NameNode。 DataNode 以固定大小的 block 为基本单位组织文件内容, 默认情况下 block 大小为 64MB。 当用户上传一个大的文件到 HDFS 上时, 该文件会被切分成若干个 block,分别存储到不同的 DataNode ; 同时,为了保证数据可靠, 会将同一个 block 以流水线方式写到若干个(默认是 3,该参数可配置)不同的 DataNode 上。 这种文件切割后存储的过程是对用户透明的。

MapReduce

同 HDFS 一样,Hadoop MapReduce 也采用了 Master/Slave(M/S)架构,具体如图所示。它主要由以下几个组件组成:Client、JobTracker、TaskTracker 和 Task。 下面分别对这几个组件进行介绍。

MapReduce.jpg

Client

用户编写的 MapReduce 程序通过 Client 提交到 JobTracker 端; 同时, 用户可通过 Client 提供的一些接口查看作业运行状态。 在 Hadoop 内部用“作业”(Job) 表示 MapReduce 程序。一个 MapReduce 程序可对应若干个作业,而每个作业会被分解成若干个 Map/Reduce 任务

(Task)。

JobTracker

JobTracker 主要负责资源监控和作业调度。JobTracker 监控所有 TaskTracker 与作业的健康状况,一旦发现失败情况后,其会将相应的任务转移到其他节点;同时 JobTracker 会跟踪任务的执行进度、资源使用量等信息,并将这些信息告诉任务调度器,而调度器会在资源出现空闲时,选择合适的任务使用这些资源。在 Hadoop 中,任务调度器是一个可插拔的模块,用户可以根据自己的需要设计相应的调度器。

TaskTracker

TaskTracker 会周期性地通过 Heartbeat 将本节点上资源的使用情况和任务的运行进度汇报给 JobTracker, 同时接收 JobTracker 发送过来的命令并执行相应的操作(如启动新任务、 杀死任务等)。TaskTracker 使用“slot” 等量划分本节点上的资源量。“slot” 代表计算资源(CPU、内存等)。一个 Task 获取到一个 slot 后才有机会运行,而 Hadoop 调度器的作用就是将各个

TaskTracker 上的空闲 slot 分配给 Task 使用。 slot 分为 Map slot 和 Reduce slot 两种,分别供 MapTask 和 Reduce Task 使用。 TaskTracker 通过 slot 数目(可配置参数)限定 Task 的并发度。

Task

Task 分为 Map Task 和 Reduce Task 两种, 均由 TaskTracker 启动。 HDFS 以固定大小的 block 为基本单位存储数据, 而对于 MapReduce 而言, 其处理单位是 split。split 与 block 的对应关系如图所示。 split 是一个逻辑概念, 它只包含一些元数据信息, 比如数据起始位置、数据长度、数据所在节点等。它的划分方法完全由用户自己决定。 但需要注意的是,split 的多少决定了 Map

Task 的数目 ,因为每个 split 会交由一个 Map Task 处理。

Map Task 执行过程如图所示。 由该图可知,Map Task 先将对应的 split 迭代解析成一个个 key/value 对,依次调用用户自定义的 map() 函数进行处理,最终将临时结果存放到本地磁盘上,其中临时数据被分成若干个 partition,每个 partition 将被一个 Reduce Task 处理。

Map_Task执行流程.jpg

Reduce Task 执行过程

该过程分为三个阶段

  1. 从远程节点上读取 MapTask 中间结果(称为“Shuffle 阶段”);
  2. 按照 key 对 key/value 对进行排序(称为“ Sort 阶段”);
  3. 依次读取,调用用户自定义的 reduce() 函数处理,并将最终结果存到 HDFS 上(称为“ Reduce 阶段”)。

Hadoop MapReduce 作业的生命周期

*_1._作业提交与初始化

  1. 用户提交作业后, 首先由 JobClient 实例将作业相关信息, 比如将程序 jar 包、作业配置文件、 分片元信息文件等上传到分布式文件系统( 一般为 HDFS)上,其中,分片元信息文件记录了每个输入分片的逻辑位置信息。 然后 JobClient 通过 RPC 通知 JobTracker。 JobTracker 收到新作业提交请求后, 由 作业调度模块对作业进行初始化:为作业创建一个 JobInProgress 对象以跟踪作业运行状况, 而 JobInProgress 则会为每个 Task 创建一个 TaskInProgress 对象以跟踪每个任务的运行状态, TaskInProgress 可能需要管理多个

“ Task 运行尝试”( 称为“ Task Attempt”)。

*_2._任务调度与监控。

  1. 前面提到,任务调度和监控的功能均由 JobTracker 完成。TaskTracker 周期性地通过

Heartbeat 向 JobTracker 汇报本节点的资源使用 情况, 一旦出 现空闲资源, JobTracker 会按照一定的策略选择一个合适的任务使用该空闲资源, 这由任务调度器完成。 任务调度器是一个可插拔的独立模块, 且为双层架构, 即首先选择作业, 然后从该作业中选择任务, 其中,选择任务时需要重点考虑数据本地性。 此外,JobTracker 跟踪作业的整个运行过程,并为作业的成功运行提供全方位的保障。 首先, 当 TaskTracker 或者 Task 失败时, 转移计算任务 ; 其次, 当某个 Task 执行进度远落后于同一作业的其他 Task 时,为之启动一个相同

Task, 并选取计算快的 Task 结果作为最终结果。

*_3._任务运行环境准备

  1. 运行环境准备包括 JVM 启动和资源隔 离, 均由 TaskTracker 实现。 TaskTracker 为每个

Task 启动一个独立的 JVM 以避免不同 Task 在运行过程中相互影响 ; 同时,TaskTracker 使用了操作系统进程实现资源隔离以防止 Task 滥用资源。

*_4._任务执行

  1. TaskTracker 为 Task 准备好运行环境后, 便会启动 Task。 在运行过程中, 每个 Task 的最

新进度首先由 Task 通过 RPC 汇报给 TaskTracker, 再由 TaskTracker 汇报给 JobTracker。

*_5._作业完成。

  1. 待所有 Task 执行完毕后, 整个作业执行成功。

Spark

概念

Spark 提供了一个全面、统一的框架用于管理各种有着不同性质(文本数据、图表数据等)的数据集和数据源(批量数据或实时的流数据)的大数据处理的需求。

核心架构

Spark.jpg

*_Spark SQL_

提供通过 Apache Hive 的 SQL 变体 Hive 查询语言(HiveQL)与 Spark 进行交互的 API。每个数据库表被当做一个 RDD,Spark SQL 查询被转换为 Spark 操作。

*_Spark Streaming_

对实时数据流进行处理和控制。Spark Streaming 允许程序能够像普通 RDD 一样处理实时数据

*_Mllib_

一个常用机器学习算法库,算法被实现为对 RDD 的 Spark 操作。这个库包含可扩展的学习算法,比如分类、回归等需要对大量数据集进行迭代的操作。

*_GraphX_

控制图、并行图操作和计算的一组算法和工具的集合。GraphX 扩展了 RDD API,包含控制图、创建子图、访问路径上所有顶点的操作

核心组件

核心组件.jpg

Cluster Manager-*制整个集群,监控worker**

在 standalone 模式中即为 Master 主节点,控制整个集群,监控 worker。在 YARN 模式中为资源管理器

Worker*节点-*负责控制计算节点从节点,负责控制计算节点,启动 Executor 或者 Driver。

Driver*: 运行Application**_main()_函数

Executor*:执行器,是为某个Application* 运行在*_worker node_ 上的一个进程

SPARK编程模型

SPARK编程模型.jpg

Spark 应用程序从编写到提交、执行、输出的整个过程如图所示,图中描述的步骤如下:

  1. 用户使用SparkContext提供的API(常用的有textFile、sequenceFile、runJob、stop等)编写 Driver application 程序。此外 SQLContext、HiveContext 及 StreamingContext 对 SparkContext 进行封装,并提供了 SQL、Hive 及流式计算相关的 API。
  2. 使用SparkContext提交的用户应用程序,首先会使用BlockManager和BroadcastManager 将任务的 Hadoop 配置进行广播。然后由 DAGScheduler 将任务转换为 RDD 并组织成 DAG, DAG 还将被划分为不同的 Stage。最后由 TaskScheduler 借助 ActorSystem 将任务提交给集群管理器(Cluster Manager)。
  3. 集群管理器(ClusterManager)给任务分配资源,即将具体任务分配到Worker上,Worker 创建 Executor 来处理任务的运行。Standalone、YARN、Mesos、EC2 等都可以作为 Spark 的集群管理器。

SPARK 计算模型

RDD 可以看做是对各种数据计算模型的统一抽象,Spark 的计算过程主要是 RDD 的迭代计算过程。RDD 的迭代计算过程非常类似于管道。分区数量取决于 partition 数量的设定,每个分区的数据只会在一个 Task 中计算。所有分区可以在多个机器节点的 Executor 上并行执行。

SPARK计算模型.jpg

SPARK 运行流程

SPARK运行流程.jpg

1.* 构建Spark Application* 的运行环境,启动*_SparkContext_

2.* SparkContext* 向资源管理器(可以是Standalone*,Mesos*Yarn*)申请运行Executor* 资源,并启动*_StandaloneExecutorbackend_,

3.* Executor*SparkContext* 申请Task*

4.* SparkContext* 将应用程序分发给*_Executor_

5.* SparkContext* 构建成DAG* 图,将DAG*图分解成Stage*、将Taskset* 发送给Task Scheduler*,最后由Task Scheduler*Task*发送给Executor*运行

6.* Task**_Executor_ 上运行,运行完释放所有资源

SPARK RDD 流程

SPARK_RDD流程.jpg

  1. 创建 RDD 对象
  2. DAGScheduler 模块介入运算,计算 RDD 之间的依赖关系,RDD 之间的依赖关系就形成了DAG
  3. 每一个 Job 被分为多个 Stage。划分 Stage 的一个主要依据是当前计算因子的输入是否是确定的,如果是则将其分在同一个 Stage,避免多个 Stage 之间的消息传递开销

SPARK RDD

(1) RDD 的创建方式

1) 从Hadoop文件系统(或与Hadoop兼容的其他持久化存储系统,如Hive、Cassandra、 HBase)输入(例如 HDFS)创建。

2) 从父 RDD 转换得到新 RDD。

3) 通过 parallelize 或 makeRDD 将单机数据创建为分布式 RDD。

(2) RDD 的两种操作算子(转换(Transformation)与行动(Action)) 对于 RDD 可以有两种操作算子:转换(Transformation)与行动(Action)。

转换(Transformation):Transformation操作是延迟计算的,也就是说从一个RDD转换生成另一个 RDD 的转换操作不是马上执行,需要等到有 Action 操作的时候才会真正触发运算。

Transformation.jpg

行动(Action):Action 算子会触发 Spark 提交作业(Job),并将数据输出 Spark 系统。

Action.jpg

Storm

概念

Storm 是一个免费并开源的分布式实时计算系统。利用 Storm 可以很容易做到可靠地处理无限的数据流,像 Hadoop 批量处理大数据一样,Storm 可以实时处理数据。

集群架构

集群架构.jpg

Nimbusmaster-代码分发给 Supervisor

Storm 集群的 Master 节点,负责分发用户代码,指派给具体的 Supervisor 节点上的 Worker 节点,去运行 Topology 对应的组件(Spout/Bolt)的 Task。

Supervisorslave-管理 Worker 进程的启动和终止

Storm 集群的从节点,负责管理运行在 Supervisor 节点上的每一个 Worker 进程的启动和终止。通过 Storm 的配置文件中的 supervisor.slots.ports 配置项,可以指定在一个 Supervisor 上最大允许多少个 Slot,每个 Slot 通过端口号来唯一标识,一个端口号对应一个 Worker 进程(如果该 Worker 进程被启动)。

Worker具体处理组件逻辑的进程

运行具体处理组件逻辑的进程。Worker 运行的任务类型只有两种,一种是 Spout 任务,一种是

Bolt 任务。

Task

worker中每一个spout/bolt的线程称为一个task. 在storm0.8之后,task不再与物理线程对应,不同 spout/bolt 的 task 可能会共享一个物理线程,该线程称为 executor。

ZooKeeper

用来协调 Nimbus 和 Supervisor,如果 Supervisor 因故障出现问题而无法运行 Topology,

Nimbus 会第一时间感知到,并重新分配 Topology 到其它可用的 Supervisor 上运行

编程模型(spout->tuple->bolt

strom 在运行中可分为 spout 与 bolt 两个组件,其中,数据源从 spout 开始,数据以 tuple 的方式发送到 bolt,多个 bolt 可以串连起来,一个 bolt 也可以接入多个 spot/bolt.运行时原理如下图:

编程模型.jpg

Topology

Storm 中运行的一个实时应用程序的名称。将 Spout、 Bolt 整合起来的拓扑图。定义了 Spout 和 Bolt 的结合关系、并发数量、配置等等。

Spout

在一个 topology 中获取源数据流的组件。通常情况下 spout 会从外部数据源中读取数据,然后转换为 topology 内部的源数据。

Bolt

接受数据然后执行处理的组件,用户可以在其中执行自己想要的操作。

Tuple

一次消息传递的基本单元,理解为一组消息就是一个 Tuple。

Stream

Tuple 的集合。表示数据的流向。

Topology 运行

在 Storm 中,一个实时应用的计算任务被打包作为 Topology 发布,这同 Hadoop MapReduce 任务相似。但是有一点不同的是:在 Hadoop 中,MapReduce 任务最终会执行完成后结束;而在 Storm 中,Topology 任务一旦提交后永远不会结束,除非你显示去停止任务。计算任务

Topology 是由不同的 Spouts 和 Bolts,通过数据流(Stream)连接起来的图。一个 Storm 在集群上运行一个 Topology 时,主要通过以下 3 个实体来完成 Topology 的执行工作:

*_(1). Worker_(进程)

*_(2). Executor_(线程)

*_(3). Task_

Topology运行.jpg

Worker(1 个 worker 进程执行的是 1 个 topology 的子集) 1 个 worker 进程执行的是 1 个 topology 的子集(注:不会出现 1 个 worker 为多个 topology 服务)。1 个 worker 进程会启动 1 个或多个 executor 线程来执行 1 个 topology 的 component(spout 或 bolt)。因此,1 个运行中的 topology 就是由集群中多台物理机上的多个 worker 进程组成的。

Executor(executor 是 1 个被 worker 进程启动的单独线程)

executor 是 1 个被 worker 进程启动的单独线程。每个 executor 只会运行 1 个 topology 的 1 个 component(spout 或 bolt)的 task(注:task 可以是 1 个或多个,storm 默认是 1 个 component 只生成 1 个 task,executor 线程里会在每次循环里顺序调用所有 task 实例)。

Task(最终运行 spout 或 bolt 中代码的单元)

是最终运行 spout 或 bolt 中代码的单元(注:1 个 task 即为 spout 或 bolt 的 1 个实例, executor 线程在执行期间会调用该 task 的 nextTuple 或 execute 方法)。topology 启动后,1 个 component(spout 或 bolt)的 task 数目是固定不变的,但该 component 使用的 executor 线程数可以动态调整(例如:1 个 executor 线程可以执行该 component 的 1 个或多个 task 实例)。这意味着,对于 1 个 component 存在这样的条件:#threads<=#tasks(即:线程数小于等于 task 数目)。默认情况下 task 的数目等于 executor 线程数目,即 1 个 executor 线程只运行 1 个 task。

Task.jpg

Storm Streaming Grouping

Storm 中最重要的抽象,应该就是 Stream grouping 了,它能够控制 Spot/Bolt 对应的 Task 以什么样的方式来分发 Tuple,将 Tuple 发射到目的 Spot/Bolt 对应的 Task.

StormStreamingGrouping.jpg

目前,Storm Streaming Grouping 支持如下几种类型:

huffle Grouping

随机分组,尽量均匀分布到下游 Bolt 中将流分组定义为混排。这种混排分组意味着来自 Spout 的输入将混排,或随机分发给此 Bolt 中的任务。shuffle grouping 对各个 task 的 tuple 分配的比较均匀。

Fields Grouping

按字段分组,按数据中 field 值进行分组;相同 field 值的 Tuple 被发送到相同的 Task 这种 grouping 机制保证相同 field 值的 tuple 会去同一个 task。

All grouping :广播

广播发送, 对于每一个 tuple 将会复制到每一个 bolt 中处理。

Global grouping

全局分组,Tuple 被分配到一个 Bolt 中的一个 Task,实现事务性的 Topology。Stream 中的所有的 tuple 都会发送给同一个 bolt 任务处理,所有的 tuple 将会发送给拥有最小 task_id 的 bolt 任务处理。

None grouping :不分组

不关注并行处理负载均衡策略时使用该方式,目前等同于 shuffle grouping,另外 storm 将会把 bolt 任务和他的上游提供数据的任务安排在同一个线程下。

Direct grouping :直接分组 指定分组

由 tuple 的发射单元直接决定 tuple 将发射给那个 bolt,一般情况下是由接收 tuple 的 bolt 决定接收哪个 bolt 发射的 Tuple。这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接收者的哪个 task 处理这个消息。 只有被声明为 Direct Stream 的消息流可以声明这种分组方法。而且这种消息 tuple 必须使用 emitDirect 方法来发射。消息处理者可以通过TopologyContext 来获取处理它的消息的 taskid (OutputCollector.emit 方法也会返回 taskid)。

YARN

概念

YARN 是一个资源管理、任务调度的框架,主要包含三大模块:ResourceManager(RM)、 NodeManager(NM)、ApplicationMaster(AM)。其中,ResourceManager 负责所有资源的监控、分配和管理;ApplicationMaster 负责每一个具体应用程序的调度和协调; NodeManager 负责每一个节点的维护。对于所有的applications,RM拥有绝对的控制权和对资源的分配权。而每个 AM 则会和 RM 协商资源,同时和 NodeManager 通信来执行和监控 task。几个模块之间的关系如图所示。

概念.png

ResourceManager

  1. ResourceManager 负责整个集群的资源管理和分配,是一个全局的资源管理系统。
  2. NodeManager 以心跳的方式向 ResourceManager 汇报资源使用情况(目前主要是 CPU 和内存的使用情况)。RM 只接受 NM 的资源回报信息,对于具体的资源处理则交给 NM 自己处理。
  3. YARN Scheduler 根据 application 的请求为其分配资源,不负责 application job 的监控、追踪、运行状态反馈、启动等工作。

NodeManager

  1. NodeManager 是每个节点上的资源和任务管理器,它是管理这台机器的代理,负责该节点程序的运行,以及该节点资源的管理和监控。YARN集群每个节点都运行一个NodeManager。
  2. NodeManager 定时向 ResourceManager 汇报本节点资源(CPU、内存)的使用情况和 Container 的运行状态。当 ResourceManager 宕机时 NodeManager 自动连接 RM 备用节点。
  3. NodeManager 接收并处理来自 ApplicationMaster 的 Container 启动、停止等各种请求。

ApplicationMaster

用户提交的每个应用程序均包含一个ApplicationMaster,它可以运行在ResourceManager以外的机器上。

  1. 负责与 RM 调度器协商以获取资源(用 Container 表示)。
  2. 将得到的任务进一步分配给内部的任务(资源的二次分配)。
  3. 与 NM 通信以启动/停止任务。
  4. 监控所有任务运行状态,并在任务运行失败时重新为任务申请资源以重启任务。
  5. 当前 YARN 自带了两个 ApplicationMaster 实现,一个是用于演示 AM 编写方法的实例程序 DistributedShell,它可以申请一定数目的 Container 以并行运行一个 Shell 命令或者 Shell 脚本;另一个是运行 MapReduce 应用程序的 AM—MRAppMaster。

注:RM 只负责监控 AM,并在 AM 运行失败时候启动它。RM 不负责 AM 内部任务的容错,任务的容错由 AM 完成。

YARN运行流程

YARN运行流程.png

  1. client 向 RM 提交应用程序,其中包括启动该应用的 ApplicationMaster 的必须信息,例如 ApplicationMaster 程序、启动 ApplicationMaster 的命令、用户程序等。
  2. ResourceManager 启动一个 container 用于运行 ApplicationMaster。
  3. 启动中的ApplicationMaster向ResourceManager注册自己,启动成功后与RM保持心跳。
  4. ApplicationMaster 向 ResourceManager 发送请求,申请相应数目的 container。
  5. ResourceManager 返回 ApplicationMaster 的申请的 containers 信息。申请成功的 container,由 ApplicationMaster 进行初始化。container 的启动信息初始化后,AM 与对应的 NodeManager 通信,要求 NM 启动 container。AM 与 NM 保持心跳,从而对 NM 上运行的任务进行监控和管理。
  6. container 运行期间,ApplicationMaster 对 container 进行监控。container 通过 RPC 协议向对应的 AM 汇报自己的进度和状态等信息。
  7. 应用运行期间,client 直接与 AM 通信获取应用的状态、进度更新等信息。
  8. 应用运行结束后,ApplicationMaster 向 ResourceManager 注销自己,并允许属于它的 container 被收回。

机器学习

决策树

随机森林算法

逻辑回归

SVM

朴素贝叶斯

K 最近邻算法

K 均值算法

Adaboost 算法

神经网络

马尔可夫

云计算

SaaS

SaaS 是 Software-as-a-Service(软件即服务)

PaaS

PaaS 是 Platform-as-a-Service 的缩写,意思是平台即服务。 把服务器平台作为一种服务提供的商业模式。通过网络进行程序提供的服务称之为 SaaS(Software as a Service),而云计算时代相应的服务器平台或者开发环境作为服务进行提供就成为了 PaaS(Platform as a Service)。

IaaS

IaaS(Infrastructure as a Service),即基础设施即服务。提供给消费者的服务是对所有设施的利用,包括处理、存储、网络和其它基本的计算资源,用户能够部署和运行任意软件,包括操作系统和应用程序。

云计算.jpg

Docker

概念

Docker 镜像 (Images) Docker 镜像是用于创建 Docker 容器的模板。
Docker 容器 (Container) 容器是独立运行的一个或一组应用。
Docker 客户端 (Client) Docker 客户端通过命令行或者其他工具使用 Docker API 与 Docker 的守护进程通信。
Docker 主机 (Host) 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker 仓库 (Registry) Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。 Docker Hub 提供了庞大的镜像集合供使用。
Docker Machine Docker Machine 是一个简化 Docker 安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装 Docker,比如 VirtualBox、 Digital Ocean、Microsoft Azure。

Docker 的出现一定是因为目前的后端在开发和运维阶段确实需要一种虚拟化技术解决开发环境和生产环境环境一致的问题,通过 Docker 我们可以将程序运行的环境也纳入到版本控制中,排除因为环境造成不同运行结果的可能。但是上述需求虽然推动了虚拟化技术的产生,但是如果没有合适的底层技术支撑,那么我们仍然得不到一个完美的产品。本文剩下的内容会介绍几种 Docker 使用的核心技术,如果我们了解它们的使用方法和原理,就能清楚 Docker 的实现原理。Docker 使用客户端-服务器 (C/S) 架构模式,使用远程 API 来管理和创建 Docker 容器。Docker 容器通过Docker 镜像来创建。

Docker.png

Namespaces

命名空间(namespaces)是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。在日常使用 Linux 或者 macOS 时,我们并没有运行多个完全分离的服务器的需要,但是如果我们在服务器上启动了多个服务,这些服务其实会相互影响的,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,这是很多时候我们都不愿意看到的,

我们更希望运行在同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样。

Linux 的命名空间机制提供了以下七种不同的命名空间,包括 CLONE_NEWCGROUP、

CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、

CLONE_NEWUSER 和 CLONE_NEWUTS,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。

进程(CLONE_NEWPID 实现的进程隔离)

docker 创建新进程时传入 CLONE_NEWPID 实现的进程隔离,也就是使用 Linux 的命名空间实现进程的隔离,Docker 容器内部的任意进程都对宿主机器的进程一无所知。当我们每次运行 docker run 或者 docker start 时,都会在创建一个用于设置进程间隔离的 Spec,同时会设置进程相关的命名空间,还会设置与用户、网络、IPC 以及 UTS 相关的命名空间,所有命名空间相关的设置 Spec 最后都会作为 Create 函数的入参在创建新的容器时进行设置。

Libnetwork 与网络隔离

如果 Docker 的容器通过 Linux 的命名空间完成了与宿主机进程的网络隔离,但是却有没有办法通过宿主机的网络与整个互联网相连,就会产生很多限制,所以 Docker 虽然可以通过命名空间创建一个隔离的网络环境,但是 Docker 中的服务仍然需要与外界相连才能发挥作用。

Docker 整个网络部分的功能都是通过 Docker 拆分出来的 libnetwork 实现的,它提供了一个连接不同容器的实现,同时也能够为应用给出一个能够提供一致的编程接口和网络层抽象的容器网络模型。

libnetwork 中最重要的概念,容器网络模型由以下的几个主要组件组成,分别是 Sandbox、 Endpoint 和 Network。在容器网络模型中,每一个容器内部都包含一个 Sandbox,其中存储着当前容器的网络栈配置,包括容器的接口、路由表和 DNS 设置,Linux 使用网络命名空间实现这个 Sandbox,每一个 Sandbox 中都可能会有一个或多个 Endpoint,在 Linux 上就是一个虚拟的网卡 veth,Sandbox 通过 Endpoint 加入到对应的网络中,这里的网络可能就是我们在上面提到的 Linux 网桥或者 VLAN。

每一个使用 docker run 启动的容器其实都具有单独的网络命名空间,Docker 为我们提供了四种不同的网络模式,Host、Container、None 和 Bridge 模式。

DOCKER_NETWORK.jpg

在这一部分,我们将介绍 Docker 默认的网络设置模式:网桥模式。在这种模式下,除了分配隔离的网络命名空间之外,Docker 还会为所有的容器设置 IP 地址。当 Docker 服务器在主机上启动之后会创建新的虚拟网桥 docker0,随后在该主机上启动的全部服务在默认情况下都与该网桥相连。在默认情况下,每一个容器在创建时都会创建一对虚拟网卡,两个虚拟网卡组成了数据的通道,其中一个会放在创建的容器中,会加入到名为 docker0 网桥中。

DOCKER_NETWORK_TOPOLOGY.jpg

资源隔离与 CGroups

Control Groups(简称 CGroups)能够隔离宿主机器上的物理资源,例如 CPU、内存、磁盘 I/O 和网络带宽。每一个 CGroup 都是一组被相同的标准和参数限制的进程,不同的 CGroup 之间是有层级关系的,也就是说它们之间可以从父类继承一些用于限制资源使用的标准和参数。

镜像与 UnionFS

Linux 的命名空间和控制组分别解决了不同资源隔离的问题,前者解决了进程、网络以及文件系统的隔离,后者实现了 CPU、内存等资源的隔离,但是在 Docker 中还有另一个非常重要的问题需要解决 - 也就是镜像。

Docker 镜像其实本质就是一个压缩包,我们可以使用命令将一个 Docker 镜像中的文件导出,你可以看到这个镜像中的目录结构与 Linux 操作系统的根目录中的内容并没有太多的区别,可以说

Docker 镜像就是一个文件。

存储驱动

Docker 使用了一系列不同的存储驱动管理镜像内的文件系统并运行容器,这些存储驱动与

Docker 卷(volume)有些不同,存储引擎管理着能够在多个容器之间共享的存储。

当镜像被 docker run 命令创建时就会在镜像的最上层添加一个可写的层,也就是容器层,所有对于运行时容器的修改其实都是对这个容器读写层的修改。容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器

UnionFS 其实是一种为 Linux 操作系统设计的用于把多个文件系统『联合』到同一个挂载点的文件系统服务。而 AUFS 即 Advanced UnionFS 其实就是 UnionFS 的升级版,它能够提供更优秀的性能和效率。

AUFS 只是 Docker 使用的存储驱动的一种,除了 AUFS 之外,Docker 还支持了不同的存储驱动,包括 aufs、devicemapper、overlay2、zfs 和 vfs 等等,在最新的 Docker 中,overlay2 取代了 aufs 成为了推荐的存储驱动,但是在没有 overlay2 驱动的机器上仍然会使用 aufs 作为 Docker 的默认驱动。

Openstack