@ -1,7 +1,7 @@
<!DOCTYPE HTML>
< html lang = "zh-hans" >
< head >
< head >
< meta charset = "UTF-8" >
< meta content = "text/html; charset=utf-8" http-equiv = "Content-Type" >
< title > 系统架构分析 · 调度系统-EasyScheduler< / title >
@ -61,23 +61,23 @@
< / head >
< body >
< / head >
< body >
< div class = "book" >
< div class = "book-summary" >
< div id = "book-search-input" role = "search" >
< div id = "book-search-input" role = "search" >
< input type = "text" placeholder = "输入并搜索" / >
< / div >
< / div >
< nav role = "navigation" >
< ul class = "summary" >
< ul class = "summary" >
@ -762,7 +762,7 @@
本书使用 GitBook 发布
< / a >
< / li >
< / ul >
< / ul >
< / nav >
@ -776,7 +776,7 @@
< div class = "book-header" role = "navigation" >
< div class = "book-header" role = "navigation" >
<!-- Title -->
@ -784,7 +784,7 @@
< i class = "fa fa-circle-o-notch fa-spin" > < / i >
< a href = ".." > 系统架构分析< / a >
< / h1 >
< / div >
< / div >
@ -792,162 +792,162 @@
< div class = "page-wrapper" tabindex = "-1" role = "main" >
< div class = "page-inner" >
< div id = "book-search-results" >
< div id = "book-search-results" >
< div class = "search-noresults" >
< section class = "normal markdown-section" >
< h1 id = "easyscheduler大数据调度系统架构分析" > EasyScheduler大 数 据 调 度 系 统 架 构 分 析 < / h1 >
< h2 id = "架构设计" > 架 构 设 计 < / h2 >
< p > < img src = "../../images/esr_2.png" alt = "PNG" > < / p >
< blockquote >
< h4 id = "easyscheduler设计围绕四个服务展开,ui、web、server和alert。" > EasyScheduler设 计 围 绕 四 个 服 务 展 开 , UI、 Web、 Server和 Alert。 < / h4 >
< ul >
< li > < h5 id = "ui--使用易观封装的vue及jsplumb组件开发" > UI : 使 用 易 观 封 装 的 Vue及 jsplumb组 件 开 发 < / h5 >
< / li >
< li > < h5 id = "web:使用springboot提供rest-api和ui分离交互" > Web: 使 用 SpringBoot提 供 Rest Api和 UI分 离 交 互 < / h5 >
< / li >
< li > < h5 id = "server--scheduler调度及分布式任务执行引擎" > Server : Scheduler调 度 及 分 布 式 任 务 执 行 引 擎 < / h5 >
< / li >
< li > < h5 id = "alert:告警微服务" > Alert: 告 警 微 服 务 < / h5 >
< / li >
< / ul >
< / blockquote >
< h2 id = "以下将详细介绍server的设计思想和遇到的问题" > 以 下 将 详 细 介 绍 Server的 设 计 思 想 和 遇 到 的 问 题 < / h2 >
< h2 id = "去中心化vs中心化" > 去 中 心 化 vs中 心 化 < / h2 >
< div id = "中心化思想" > < / div >
< h3 id = "1-中心化思想" > 1. 中 心 化 思 想 < / h3 >
< blockquote >
< h4 id = "       中心化的设计理念比较简单,分布式集群中的节点按照角色分工,大体上分为两种角色:" >               中 心 化 的 设 计 理 念 比 较 简 单 , 分 布 式 集 群 中 的 节 点 按 照 角 色 分 工 , 大 体 上 分 为 两 种 角 色 : < / h4 >
< p > < img src = "../../images/esr_3.png" alt = "PNG" > < / p >
< h4 id = "       master的角色主要负责任务分发并监督slave的健康状态,可以动态的将任务均衡到slave上,以致slave节点不至于忙死或闲死的状态。" >               Master的 角 色 主 要 负 责 任 务 分 发 并 监 督 Slave的 健 康 状 态 , 可 以 动 态 的 将 任 务 均 衡 到 Slave上 , 以 致 Slave节 点 不 至 于 “ 忙 死 ” 或 ” 闲 死 ” 的 状 态 。 < / h4 >
< h4 id = "       worker的角色主要负责任务的执行工作并维护和master的心跳,以便master可以分配任务给slave。" >               Worker的 角 色 主 要 负 责 任 务 的 执 行 工 作 并 维 护 和 Master的 心 跳 , 以 便 Master可 以 分 配 任 务 给 Slave。 < / h4 >
< / blockquote >
< h3 id = "2-中心化思想设计存在的问题:" > 2. 中 心 化 思 想 设 计 存 在 的 问 题 : < / h3 >
< blockquote >
< h4 id = "       一旦master出现了问题,则群龙无首,整个集群就崩溃。为了解决这个问题,大多数masterslave架构模式都采用了主备master的设计方案,可以是热备或者冷备,也可以是自动切换或手动切换,而且越来越多的新系统都开始具备自动选举切换master的能力以提升系统的可用性。" >               一 旦 Master出 现 了 问 题 , 则 群 龙 无 首 , 整 个 集 群 就 崩 溃 。 为 了 解 决 这 个 问 题 , 大 多 数 Master/Slave架 构 模 式 都 采 用 了 主 备 Master的 设 计 方 案 , 可 以 是 热 备 或 者 冷 备 , 也 可 以 是 自 动 切 换 或 手 动 切 换 , 而 且 越 来 越 多 的 新 系 统 都 开 始 具 备 自 动 选 举 切 换 Master的 能 力 ,以 提 升 系 统 的 可 用 性 。 < / h4 >
< h4 id = "       另外一个问题是如果scheduler在master上,虽然可以支持一个dag中不同的任务运行在不同的机器上,但是会产生master的过负载。如果scheduler在slave上,则一个dag中所有的任务都只能在某一台机器上进行作业提交,则并行任务比较多的时候,slave的压力可能会比较大。" >               另 外 一 个 问 题 是 如 果 Scheduler在 Master上 , 虽 然 可 以 支 持 一 个 DAG中 不 同 的 任 务 运 行 在 不 同 的 机 器 上 , 但 是 会 产 生 Master的 过 负 载 。 如 果 Scheduler在 Slave上 , 则 一 个 DAG中 所 有 的 任 务 都 只 能 在 某 一 台 机 器 上 进 行 作 业 提 交 , 则 并 行 任 务 比 较 多 的 时 候 , Slave的 压 力 可 能 会 比 较 大 。 < / h4 >
< / blockquote >
< h3 id = "3-去中心化" > 3. 去 中 心 化 < / h3 >
< blockquote >
< p > < img src = "../../images/esr_4.png" alt = "PNG" > < / p >
< h4 id = "       去中心化设计里,通常没有masterslave的概念,所有的角色都是一样的,地位是平等的,全球互联网就是一个典型的去中心化的分布式系统,联网的任意节点设备down机,都只会影响很小范围的功能。" >               去 中 心 化 设 计 里 , 通 常 没 有 Master/Slave的 概 念 , 所 有 的 角 色 都 是 一 样 的 , 地 位 是 平 等 的 , 全 球 互 联 网 就 是 一 个 典 型 的 去 中 心 化 的 分 布 式 系 统 , 联 网 的 任 意 节 点 设 备 down机 , 都 只 会 影 响 很 小 范 围 的 功 能 。 < / h4 >
< h4 id = "       去中心化设计的核心设计在于整个分布式系统中不存在一个区别于其他节点的管理者,因此不存在单点故障问题。但由于不存在-管理者节点所以每个节点都需要跟其他节点通信才得到必须要的机器信息,而分布式系统通信的不可靠行,则大大增加了上述功能的实现难度。" >               去 中 心 化 设 计 的 核 心 设 计 在 于 整 个 分 布 式 系 统 中 不 存 在 一 个 区 别 于 其 他 节 点 的 ” 管 理 者 ” , 因 此 不 存 在 单 点 故 障 问 题 。 但 由 于 不 存 在 ” 管 理 者 ” 节 点 所 以 每 个 节 点 都 需 要 跟 其 他 节 点 通 信 才 得 到 必 须 要 的 机 器 信 息 , 而 分 布 式 系 统 通 信 的 不 可 靠 行 , 则 大 大 增 加 了 上 述 功 能 的 实 现 难 度 。 < / h4 >
< h4 id = "       实际上,真正去中心化的分布式系统并不多见。反而动态中心化分布式系统正在不断涌出。在这种架构下,集群中的管理者是被动态选择出来的,而不是预置的,并且集群在发生故障的时候,集群的节点会自发的举行会议选举新的管理者主持工作。最典型的案例就是zookeeper及go语言实现的etcd。" >               实 际 上 , 真 正 去 中 心 化 的 分 布 式 系 统 并 不 多 见 。 反 而 动 态 中 心 化 分 布 式 系 统 正 在 不 断 涌 出 。 在 这 种 架 构 下 , 集 群 中 的 管 理 者 是 被 动 态 选 择 出 来 的 , 而 不 是 预 置 的 , 并 且 集 群 在 发 生 故 障 的 时 候 , 集 群 的 节 点 会 自 发 的 举 行 " 会 议 " 选 举 新 的 " 管 理 者 " 主 持 工 作 。 最 典 型 的 案 例 就 是 ZooKeeper及 Go语 言 实 现 的 Etcd。 < / h4 >
< / blockquote >
< p >   < / p >
< blockquote >
< h3 id = "easyscheduler的去中心化是masterworker注册到zookeeper中,实现master集群和worker集群无中心,并使用zookeeper分布式锁来选举其中的一台master或worker为管理者来执行任务。" > EasyScheduler的 去 中 心 化 是 Master/Worker注 册 到 Zookeeper中 , 实 现 Master集 群 和 Worker集 群 无 中 心 , 并 使 用 Zookeeper分 布 式 锁 来 选 举 其 中 的 一 台 Master或 Worker为 “ 管 理 者 ” 来 执 行 任 务 。 < / h3 >
< / blockquote >
< h2 id = "分布式锁实践" > 分 布 式 锁 实 践 < / h2 >
< h3 id = "easyscheduler使用zookeeper分布式锁来实现同一时刻只有一台master执行scheduler,或者只有一台worker执行任务的提交。" > EasyScheduler使 用 Zookeeper分 布 式 锁 来 实 现 同 一 时 刻 只 有 一 台 Master执 行 Scheduler, 或 者 只 有 一 台 Worker执 行 任 务 的 提 交 。 < / h3 >
< h3 id = "1-获取分布式锁的核心流程算法如下:" > 1. 获 取 分 布 式 锁 的 核 心 流 程 算 法 如 下 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_5.png" alt = "PNG" > < / p >
< / blockquote >
< h3 id = "2-easyscheduler中scheduler线程分布式锁实现流程图:" > 2. EasyScheduler中 Scheduler线 程 分 布 式 锁 实 现 流 程 图 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_6.png" alt = "PNG" > < / p >
< / blockquote >
< h2 id = "线程不足循环等待问题" > 线 程 不 足 循 环 等 待 问 题 < / h2 >
< ul >
< li > < h3 id = "如果一个dag中没有子流程,则如果command中的数据条数大于线程池设置的阈值,则直接流程等待或失败。" > 如 果 一 个 DAG中 没 有 子 流 程 , 则 如 果 Command中 的 数 据 条 数 大 于 线 程 池 设 置 的 阈 值 , 则 直 接 流 程 等 待 或 失 败 。 < / h3 >
< / li >
< li > < h3 id = "如果一个大的dag中嵌套了很多子流程,如下图:" > 如 果 一 个 大 的 DAG中 嵌 套 了 很 多 子 流 程 , 如 下 图 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_7.png" alt = "PNG" > < / p >
< h4 id = "       则会产生死等状态。mainflowthread等待subflowthread1结束,subflowthread1等待subflowthread2结束,subflowthread2等待subflowthread3结束,而subflowthread3等待线程池有新线程,则整个dag流程不能结束,从而其中的线程也不能释放。" >               则 会 产 生 “ 死 等 ” 状 态 。 MainFlowThread等 待 SubFlowThread1结 束 , SubFlowThread1等 待 SubFlowThread2结 束 , SubFlowThread2等 待 SubFlowThread3结 束 , 而 SubFlowThread3等 待 线 程 池 有 新 线 程 , 则 整 个 DAG流 程 不 能 结 束 , 从 而 其 中 的 线 程 也 不 能 释 放 。 < / h4 >
< h4 id = "       这样就形成的子父流程循环等待的状态。此时除非启动新的master来增加线程来打破这样的僵局,否则调度集群将不能再使用。" >               这 样 就 形 成 的 子 父 流 程 循 环 等 待 的 状 态 。 此 时 除 非 启 动 新 的 Master来 增 加 线 程 来 打 破 这 样 的 ” 僵 局 ” , 否 则 调 度 集 群 将 不 能 再 使 用 。 < / h4 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "对于启动新master来打破僵局,似乎有点差强人意,于是我们提出了以下三种方案来降低这种风险:" > 对 于 启 动 新 Master来 打 破 僵 局 , 似 乎 有 点 差 强 人 意 , 于 是 我 们 提 出 了 以 下 三 种 方 案 来 降 低 这 种 风 险 : < / h3 >
< blockquote >
< ul >
< li > < h4 id = "计算所有master的线程总和,然后对每一个dag需要计算其需要的线程数,也就是在dag流程执行之前做预计算。因为是多master线程池,所以总线程数不太可能实时获取。" > 计 算 所 有 Master的 线 程 总 和 , 然 后 对 每 一 个 DAG需 要 计 算 其 需 要 的 线 程 数 , 也 就 是 在 DAG流 程 执 行 之 前 做 预 计 算 。 因 为 是 多 Master线 程 池 , 所 以 总 线 程 数 不 太 可 能 实 时 获 取 。 < / h4 >
< / li >
< li > < h4 id = "对单master线程池进行判断,如果线程池已经满了,则让线程直接失败。" > 对 单 Master线 程 池 进 行 判 断 , 如 果 线 程 池 已 经 满 了 , 则 让 线 程 直 接 失 败 。 < / h4 >
< / li >
< li > < h4 id = "增加一种资源不足的command类型,如果线程池不足,则将主流程挂起。这样线程池就有了新的线程,可以让资源不足挂起的流程重新唤醒执行。" > 增 加 一 种 资 源 不 足 的 Command类 型 , 如 果 线 程 池 不 足 , 则 将 主 流 程 挂 起 。 这 样 线 程 池 就 有 了 新 的 线 程 , 可 以 让 资 源 不 足 挂 起 的 流 程 重 新 唤 醒 执 行 。 < / h4 >
< / li >
< / ul >
< / blockquote >
< / li >
< li > < h3 id = "注意:master-scheduler线程在获取command的时候是fifo的方式执行的。" > 注 意 : Master Scheduler线 程 在 获 取 Command的 时 候 是 FIFO的 方 式 执 行 的 。 < / h3 >
< / li >
< li > < h3 id = "于是我们选择了第三种方式来解决线程不足的问题。" > 于 是 我 们 选 择 了 第 三 种 方 式 来 解 决 线 程 不 足 的 问 题 。 < / h3 >
< / li >
< / ul >
< h2 id = "容错设计" > 容 错 设 计 < / h2 >
< ul >
< li > < h3 id = "easyscheduler容错设计依赖于zookeeper的watcher机制,实现原理如图:" > EasyScheduler容 错 设 计 依 赖 于 Zookeeper的 Watcher机 制 , 实 现 原 理 如 图 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_8.png" alt = "PNG" > < / p >
< h3 id = "       master监控其他master和worker的目录,如果监听到remove事件,则会根据具体的业务逻辑进行流程实例容错或者任务实例容错。" >               Master监 控 其 他 Master和 Worker的 目 录 , 如 果 监 听 到 remove事 件 , 则 会 根 据 具 体 的 业 务 逻 辑 进 行 流 程 实 例 容 错 或 者 任 务 实 例 容 错 。 < / h3 >
< / blockquote >
< / li >
< li > < h3 id = "master容错流程图:" > Master容 错 流 程 图 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_9.png" alt = "PNG" > < / p >
< h3 id = "       zookeeper--master容错完成之后则重新由easyscheduler中scheduler线程调度,遍历-dag-找到正在运行和提交成功的任务,对正在运行的任务监控其任务实例的状态,对提交成功的任务需要判断task-queue中是否已经存在,如果存在则同样监控任务实例的状态,如果不存在则重新提交任务实例。" >               ZooKeeper Master容 错 完 成 之 后 则 重 新 由 EasyScheduler中 Scheduler线 程 调 度 , 遍 历 DAG 找 到 ” 正 在 运 行 ” 和 “ 提 交 成 功 ” 的 任 务 , 对 ” 正 在 运 行 ” 的 任 务 监 控 其 任 务 实 例 的 状 态 , 对 ” 提 交 成 功 ” 的 任 务 需 要 判 断 Task Queue中 是 否 已 经 存 在 , 如 果 存 在 则 同 样 监 控 任 务 实 例 的 状 态 , 如 果 不 存 在 则 重 新 提 交 任 务 实 例 。 < / h3 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "worker容错流程图:" > Worker容 错 流 程 图 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_10.png" alt = "PNG" > < / p >
< h3 id = "       master-scheduler线程一旦发现任务实例为-需要容错状态,则接管任务并进行重新提交。" >               Master Scheduler线 程 一 旦 发 现 任 务 实 例 为 ” 需 要 容 错 ” 状 态 , 则 接 管 任 务 并 进 行 重 新 提 交 。 < / h3 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "注意:由于-网络抖动可能会使得节点短时间内失去和zk的心跳,从而发生节点的remove事件。对于这种情况,我们使用最简单的方式,那就是节点一旦和zk发生超时连接,则直接将master或worker服务停掉。" > 注 意 : 由 于 ” 网 络 抖 动 ” 可 能 会 使 得 节 点 短 时 间 内 失 去 和 zk的 心 跳 , 从 而 发 生 节 点 的 remove事 件 。 对 于 这 种 情 况 , 我 们 使 用 最 简 单 的 方 式 , 那 就 是 节 点 一 旦 和 zk发 生 超 时 连 接 , 则 直 接 将 Master或 Worker服 务 停 掉 。 < / h3 >
< / li >
< / ul >
< h2 id = "logback和grpc实现日志访问" > Logback和 gRPC实 现 日 志 访 问 < / h2 >
< div id = "日志访问" > < / div >
< ul >
< li > < h3 id = "由于web和worker不一定在同一台机器上,所以查看日志不能像查询本地文件那样。有两种方案:" > 由 于 Web和 Worker不 一 定 在 同 一 台 机 器 上 , 所 以 查 看 日 志 不 能 像 查 询 本 地 文 件 那 样 。 有 两 种 方 案 : < / h3 >
< ul >
< li > < h4 id = "将日志放到es搜索引擎上" > 将 日 志 放 到 ES搜 索 引 擎 上 < / h4 >
< / li >
< li > < h4 id = "通过grpc通信获取远程日志信息" > 通 过 gRPC通 信 获 取 远 程 日 志 信 息 < / h4 >
< / li >
< / ul >
< / li >
< li > < h3 id = "介于考虑到尽可能的easyscheduler的轻量级性,所以选择了grpc实现远程访问日志信息。" > 介 于 考 虑 到 尽 可 能 的 EasyScheduler的 轻 量 级 性 , 所 以 选 择 了 gRPC实 现 远 程 访 问 日 志 信 息 。 < / h3 >
< blockquote >
< p > < img src = "../../images/esr_11.png" alt = "PNG" > < / p >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "我们使用自定义logback的fileappender和filter功能,实现每个任务实例生成一个日志文件。" > 我 们 使 用 自 定 义 Logback的 FileAppender和 Filter功 能 , 实 现 每 个 任 务 实 例 生 成 一 个 日 志 文 件 。 < / h3 >
< / li >
< / ul >
< ul >
< li > < h3 id = "fileappender实现如下:" > FileAppender实 现 如 下 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_12.png" alt = "PNG" > < / p >
< h4 id = "以流程定义id流程实例id任务实例idlog的形式生成日志。" > 以 … /流 程 定 义 id/流 程 实 例 id/任 务 实 例 id.log的 形 式 生 成 日 志 。 < / h4 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "过滤匹配以taskloginfo开始的线程名称:" > 过 滤 匹 配 以 TaskLogInfo开 始 的 线 程 名 称 : < / h3 >
< blockquote >
< p > < img src = "../../images/esr_13.png" alt = "PNG" > < / p >
< / blockquote >
< / li >
< / ul >
< h2 id = "总结" > 总 结 < / h2 >
< blockquote >
< h3 id = "本文从调度出发,介绍了易观研发的大数据分布式工作流调度系统。easyscheduler在易观数据平台起着中流砥柱的作用。本章着重介绍了easyscheduler的架构原理及实现思路。" > 本 文 从 调 度 出 发 , 介 绍 了 易 观 研 发 的 大 数 据 分 布 式 工 作 流 调 度 系 统 。 EasyScheduler在 易 观 数 据 平 台 起 着 中 流 砥 柱 的 作 用 。 本 章 着 重 介 绍 了 EasyScheduler的 架 构 原 理 及 实 现 思 路 。 < / h3 >
< / blockquote >
< h2 id = "架构设计" > 架 构 设 计 < / h2 >
< p > < img src = "../ ../../images/esr_2.png" alt = "PNG" > < / p >
< blockquote >
< h4 id = "easyscheduler设计围绕四个服务展开,ui、web、server和alert。" > EasyScheduler设 计 围 绕 四 个 服 务 展 开 , UI、 Web、 Server和 Alert。 < / h4 >
< ul >
< li > < h5 id = "ui--使用易观封装的vue及jsplumb组件开发" > UI : 使 用 易 观 封 装 的 Vue及 jsplumb组 件 开 发 < / h5 >
< / li >
< li > < h5 id = "web:使用springboot提供rest-api和ui分离交互" > Web: 使 用 SpringBoot提 供 Rest Api和 UI分 离 交 互 < / h5 >
< / li >
< li > < h5 id = "server--scheduler调度及分布式任务执行引擎" > Server : Scheduler调 度 及 分 布 式 任 务 执 行 引 擎 < / h5 >
< / li >
< li > < h5 id = "alert:告警微服务" > Alert: 告 警 微 服 务 < / h5 >
< / li >
< / ul >
< / blockquote >
< h2 id = "以下将详细介绍server的设计思想和遇到的问题" > 以 下 将 详 细 介 绍 Server的 设 计 思 想 和 遇 到 的 问 题 < / h2 >
< h2 id = "去中心化vs中心化" > 去 中 心 化 vs中 心 化 < / h2 >
< div id = "中心化思想" > < / div >
< h3 id = "1-中心化思想" > 1. 中 心 化 思 想 < / h3 >
< blockquote >
< h4 id = "       中心化的设计理念比较简单,分布式集群中的节点按照角色分工,大体上分为两种角色:" >               中 心 化 的 设 计 理 念 比 较 简 单 , 分 布 式 集 群 中 的 节 点 按 照 角 色 分 工 , 大 体 上 分 为 两 种 角 色 : < / h4 >
< p > < img src = "../ ../../images/esr_3.png" alt = "PNG" > < / p >
< h4 id = "       master的角色主要负责任务分发并监督slave的健康状态,可以动态的将任务均衡到slave上,以致slave节点不至于忙死或闲死的状态。" >               Master的 角 色 主 要 负 责 任 务 分 发 并 监 督 Slave的 健 康 状 态 , 可 以 动 态 的 将 任 务 均 衡 到 Slave上 , 以 致 Slave节 点 不 至 于 “ 忙 死 ” 或 ” 闲 死 ” 的 状 态 。 < / h4 >
< h4 id = "       worker的角色主要负责任务的执行工作并维护和master的心跳,以便master可以分配任务给slave。" >               Worker的 角 色 主 要 负 责 任 务 的 执 行 工 作 并 维 护 和 Master的 心 跳 , 以 便 Master可 以 分 配 任 务 给 Slave。 < / h4 >
< / blockquote >
< h3 id = "2-中心化思想设计存在的问题:" > 2. 中 心 化 思 想 设 计 存 在 的 问 题 : < / h3 >
< blockquote >
< h4 id = "       一旦master出现了问题,则群龙无首,整个集群就崩溃。为了解决这个问题,大多数masterslave架构模式都采用了主备master的设计方案,可以是热备或者冷备,也可以是自动切换或手动切换,而且越来越多的新系统都开始具备自动选举切换master的能力以提升系统的可用性。" >               一 旦 Master出 现 了 问 题 , 则 群 龙 无 首 , 整 个 集 群 就 崩 溃 。 为 了 解 决 这 个 问 题 , 大 多 数 Master/Slave架 构 模 式 都 采 用 了 主 备 Master的 设 计 方 案 , 可 以 是 热 备 或 者 冷 备 , 也 可 以 是 自 动 切 换 或 手 动 切 换 , 而 且 越 来 越 多 的 新 系 统 都 开 始 具 备 自 动 选 举 切 换 Master的 能 力 ,以 提 升 系 统 的 可 用 性 。 < / h4 >
< h4 id = "       另外一个问题是如果scheduler在master上,虽然可以支持一个dag中不同的任务运行在不同的机器上,但是会产生master的过负载。如果scheduler在slave上,则一个dag中所有的任务都只能在某一台机器上进行作业提交,则并行任务比较多的时候,slave的压力可能会比较大。" >               另 外 一 个 问 题 是 如 果 Scheduler在 Master上 , 虽 然 可 以 支 持 一 个 DAG中 不 同 的 任 务 运 行 在 不 同 的 机 器 上 , 但 是 会 产 生 Master的 过 负 载 。 如 果 Scheduler在 Slave上 , 则 一 个 DAG中 所 有 的 任 务 都 只 能 在 某 一 台 机 器 上 进 行 作 业 提 交 , 则 并 行 任 务 比 较 多 的 时 候 , Slave的 压 力 可 能 会 比 较 大 。 < / h4 >
< / blockquote >
< h3 id = "3-去中心化" > 3. 去 中 心 化 < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_4.png" alt = "PNG" > < / p >
< h4 id = "       去中心化设计里,通常没有masterslave的概念,所有的角色都是一样的,地位是平等的,全球互联网就是一个典型的去中心化的分布式系统,联网的任意节点设备down机,都只会影响很小范围的功能。" >               去 中 心 化 设 计 里 , 通 常 没 有 Master/Slave的 概 念 , 所 有 的 角 色 都 是 一 样 的 , 地 位 是 平 等 的 , 全 球 互 联 网 就 是 一 个 典 型 的 去 中 心 化 的 分 布 式 系 统 , 联 网 的 任 意 节 点 设 备 down机 , 都 只 会 影 响 很 小 范 围 的 功 能 。 < / h4 >
< h4 id = "       去中心化设计的核心设计在于整个分布式系统中不存在一个区别于其他节点的管理者,因此不存在单点故障问题。但由于不存在-管理者节点所以每个节点都需要跟其他节点通信才得到必须要的机器信息,而分布式系统通信的不可靠行,则大大增加了上述功能的实现难度。" >               去 中 心 化 设 计 的 核 心 设 计 在 于 整 个 分 布 式 系 统 中 不 存 在 一 个 区 别 于 其 他 节 点 的 ” 管 理 者 ” , 因 此 不 存 在 单 点 故 障 问 题 。 但 由 于 不 存 在 ” 管 理 者 ” 节 点 所 以 每 个 节 点 都 需 要 跟 其 他 节 点 通 信 才 得 到 必 须 要 的 机 器 信 息 , 而 分 布 式 系 统 通 信 的 不 可 靠 行 , 则 大 大 增 加 了 上 述 功 能 的 实 现 难 度 。 < / h4 >
< h4 id = "       实际上,真正去中心化的分布式系统并不多见。反而动态中心化分布式系统正在不断涌出。在这种架构下,集群中的管理者是被动态选择出来的,而不是预置的,并且集群在发生故障的时候,集群的节点会自发的举行会议选举新的管理者主持工作。最典型的案例就是zookeeper及go语言实现的etcd。" >               实 际 上 , 真 正 去 中 心 化 的 分 布 式 系 统 并 不 多 见 。 反 而 动 态 中 心 化 分 布 式 系 统 正 在 不 断 涌 出 。 在 这 种 架 构 下 , 集 群 中 的 管 理 者 是 被 动 态 选 择 出 来 的 , 而 不 是 预 置 的 , 并 且 集 群 在 发 生 故 障 的 时 候 , 集 群 的 节 点 会 自 发 的 举 行 " 会 议 " 选 举 新 的 " 管 理 者 " 主 持 工 作 。 最 典 型 的 案 例 就 是 ZooKeeper及 Go语 言 实 现 的 Etcd。 < / h4 >
< / blockquote >
< p >   < / p >
< blockquote >
< h3 id = "easyscheduler的去中心化是masterworker注册到zookeeper中,实现master集群和worker集群无中心,并使用zookeeper分布式锁来选举其中的一台master或worker为管理者来执行任务。" > EasyScheduler的 去 中 心 化 是 Master/Worker注 册 到 Zookeeper中 , 实 现 Master集 群 和 Worker集 群 无 中 心 , 并 使 用 Zookeeper分 布 式 锁 来 选 举 其 中 的 一 台 Master或 Worker为 “ 管 理 者 ” 来 执 行 任 务 。 < / h3 >
< / blockquote >
< h2 id = "分布式锁实践" > 分 布 式 锁 实 践 < / h2 >
< h3 id = "easyscheduler使用zookeeper分布式锁来实现同一时刻只有一台master执行scheduler,或者只有一台worker执行任务的提交。" > EasyScheduler使 用 Zookeeper分 布 式 锁 来 实 现 同 一 时 刻 只 有 一 台 Master执 行 Scheduler, 或 者 只 有 一 台 Worker执 行 任 务 的 提 交 。 < / h3 >
< h3 id = "1-获取分布式锁的核心流程算法如下:" > 1. 获 取 分 布 式 锁 的 核 心 流 程 算 法 如 下 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_5.png" alt = "PNG" > < / p >
< / blockquote >
< h3 id = "2-easyscheduler中scheduler线程分布式锁实现流程图:" > 2. EasyScheduler中 Scheduler线 程 分 布 式 锁 实 现 流 程 图 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_6.png" alt = "PNG" > < / p >
< / blockquote >
< h2 id = "线程不足循环等待问题" > 线 程 不 足 循 环 等 待 问 题 < / h2 >
< ul >
< li > < h3 id = "如果一个dag中没有子流程,则如果command中的数据条数大于线程池设置的阈值,则直接流程等待或失败。" > 如 果 一 个 DAG中 没 有 子 流 程 , 则 如 果 Command中 的 数 据 条 数 大 于 线 程 池 设 置 的 阈 值 , 则 直 接 流 程 等 待 或 失 败 。 < / h3 >
< / li >
< li > < h3 id = "如果一个大的dag中嵌套了很多子流程,如下图:" > 如 果 一 个 大 的 DAG中 嵌 套 了 很 多 子 流 程 , 如 下 图 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_7.png" alt = "PNG" > < / p >
< h4 id = "       则会产生死等状态。mainflowthread等待subflowthread1结束,subflowthread1等待subflowthread2结束,subflowthread2等待subflowthread3结束,而subflowthread3等待线程池有新线程,则整个dag流程不能结束,从而其中的线程也不能释放。" >               则 会 产 生 “ 死 等 ” 状 态 。 MainFlowThread等 待 SubFlowThread1结 束 , SubFlowThread1等 待 SubFlowThread2结 束 , SubFlowThread2等 待 SubFlowThread3结 束 , 而 SubFlowThread3等 待 线 程 池 有 新 线 程 , 则 整 个 DAG流 程 不 能 结 束 , 从 而 其 中 的 线 程 也 不 能 释 放 。 < / h4 >
< h4 id = "       这样就形成的子父流程循环等待的状态。此时除非启动新的master来增加线程来打破这样的僵局,否则调度集群将不能再使用。" >               这 样 就 形 成 的 子 父 流 程 循 环 等 待 的 状 态 。 此 时 除 非 启 动 新 的 Master来 增 加 线 程 来 打 破 这 样 的 ” 僵 局 ” , 否 则 调 度 集 群 将 不 能 再 使 用 。 < / h4 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "对于启动新master来打破僵局,似乎有点差强人意,于是我们提出了以下三种方案来降低这种风险:" > 对 于 启 动 新 Master来 打 破 僵 局 , 似 乎 有 点 差 强 人 意 , 于 是 我 们 提 出 了 以 下 三 种 方 案 来 降 低 这 种 风 险 : < / h3 >
< blockquote >
< ul >
< li > < h4 id = "计算所有master的线程总和,然后对每一个dag需要计算其需要的线程数,也就是在dag流程执行之前做预计算。因为是多master线程池,所以总线程数不太可能实时获取。" > 计 算 所 有 Master的 线 程 总 和 , 然 后 对 每 一 个 DAG需 要 计 算 其 需 要 的 线 程 数 , 也 就 是 在 DAG流 程 执 行 之 前 做 预 计 算 。 因 为 是 多 Master线 程 池 , 所 以 总 线 程 数 不 太 可 能 实 时 获 取 。 < / h4 >
< / li >
< li > < h4 id = "对单master线程池进行判断,如果线程池已经满了,则让线程直接失败。" > 对 单 Master线 程 池 进 行 判 断 , 如 果 线 程 池 已 经 满 了 , 则 让 线 程 直 接 失 败 。 < / h4 >
< / li >
< li > < h4 id = "增加一种资源不足的command类型,如果线程池不足,则将主流程挂起。这样线程池就有了新的线程,可以让资源不足挂起的流程重新唤醒执行。" > 增 加 一 种 资 源 不 足 的 Command类 型 , 如 果 线 程 池 不 足 , 则 将 主 流 程 挂 起 。 这 样 线 程 池 就 有 了 新 的 线 程 , 可 以 让 资 源 不 足 挂 起 的 流 程 重 新 唤 醒 执 行 。 < / h4 >
< / li >
< / ul >
< / blockquote >
< / li >
< li > < h3 id = "注意:master-scheduler线程在获取command的时候是fifo的方式执行的。" > 注 意 : Master Scheduler线 程 在 获 取 Command的 时 候 是 FIFO的 方 式 执 行 的 。 < / h3 >
< / li >
< li > < h3 id = "于是我们选择了第三种方式来解决线程不足的问题。" > 于 是 我 们 选 择 了 第 三 种 方 式 来 解 决 线 程 不 足 的 问 题 。 < / h3 >
< / li >
< / ul >
< h2 id = "容错设计" > 容 错 设 计 < / h2 >
< ul >
< li > < h3 id = "easyscheduler容错设计依赖于zookeeper的watcher机制,实现原理如图:" > EasyScheduler容 错 设 计 依 赖 于 Zookeeper的 Watcher机 制 , 实 现 原 理 如 图 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_8.png" alt = "PNG" > < / p >
< h3 id = "       master监控其他master和worker的目录,如果监听到remove事件,则会根据具体的业务逻辑进行流程实例容错或者任务实例容错。" >               Master监 控 其 他 Master和 Worker的 目 录 , 如 果 监 听 到 remove事 件 , 则 会 根 据 具 体 的 业 务 逻 辑 进 行 流 程 实 例 容 错 或 者 任 务 实 例 容 错 。 < / h3 >
< / blockquote >
< / li >
< li > < h3 id = "master容错流程图:" > Master容 错 流 程 图 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_9.png" alt = "PNG" > < / p >
< h3 id = "       zookeeper--master容错完成之后则重新由easyscheduler中scheduler线程调度,遍历-dag-找到正在运行和提交成功的任务,对正在运行的任务监控其任务实例的状态,对提交成功的任务需要判断task-queue中是否已经存在,如果存在则同样监控任务实例的状态,如果不存在则重新提交任务实例。" >               ZooKeeper Master容 错 完 成 之 后 则 重 新 由 EasyScheduler中 Scheduler线 程 调 度 , 遍 历 DAG 找 到 ” 正 在 运 行 ” 和 “ 提 交 成 功 ” 的 任 务 , 对 ” 正 在 运 行 ” 的 任 务 监 控 其 任 务 实 例 的 状 态 , 对 ” 提 交 成 功 ” 的 任 务 需 要 判 断 Task Queue中 是 否 已 经 存 在 , 如 果 存 在 则 同 样 监 控 任 务 实 例 的 状 态 , 如 果 不 存 在 则 重 新 提 交 任 务 实 例 。 < / h3 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "worker容错流程图:" > Worker容 错 流 程 图 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_10.png" alt = "PNG" > < / p >
< h3 id = "       master-scheduler线程一旦发现任务实例为-需要容错状态,则接管任务并进行重新提交。" >               Master Scheduler线 程 一 旦 发 现 任 务 实 例 为 ” 需 要 容 错 ” 状 态 , 则 接 管 任 务 并 进 行 重 新 提 交 。 < / h3 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "注意:由于-网络抖动可能会使得节点短时间内失去和zk的心跳,从而发生节点的remove事件。对于这种情况,我们使用最简单的方式,那就是节点一旦和zk发生超时连接,则直接将master或worker服务停掉。" > 注 意 : 由 于 ” 网 络 抖 动 ” 可 能 会 使 得 节 点 短 时 间 内 失 去 和 zk的 心 跳 , 从 而 发 生 节 点 的 remove事 件 。 对 于 这 种 情 况 , 我 们 使 用 最 简 单 的 方 式 , 那 就 是 节 点 一 旦 和 zk发 生 超 时 连 接 , 则 直 接 将 Master或 Worker服 务 停 掉 。 < / h3 >
< / li >
< / ul >
< h2 id = "logback和grpc实现日志访问" > Logback和 gRPC实 现 日 志 访 问 < / h2 >
< div id = "日志访问" > < / div >
< ul >
< li > < h3 id = "由于web和worker不一定在同一台机器上,所以查看日志不能像查询本地文件那样。有两种方案:" > 由 于 Web和 Worker不 一 定 在 同 一 台 机 器 上 , 所 以 查 看 日 志 不 能 像 查 询 本 地 文 件 那 样 。 有 两 种 方 案 : < / h3 >
< ul >
< li > < h4 id = "将日志放到es搜索引擎上" > 将 日 志 放 到 ES搜 索 引 擎 上 < / h4 >
< / li >
< li > < h4 id = "通过grpc通信获取远程日志信息" > 通 过 gRPC通 信 获 取 远 程 日 志 信 息 < / h4 >
< / li >
< / ul >
< / li >
< li > < h3 id = "介于考虑到尽可能的easyscheduler的轻量级性,所以选择了grpc实现远程访问日志信息。" > 介 于 考 虑 到 尽 可 能 的 EasyScheduler的 轻 量 级 性 , 所 以 选 择 了 gRPC实 现 远 程 访 问 日 志 信 息 。 < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_11.png" alt = "PNG" > < / p >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "我们使用自定义logback的fileappender和filter功能,实现每个任务实例生成一个日志文件。" > 我 们 使 用 自 定 义 Logback的 FileAppender和 Filter功 能 , 实 现 每 个 任 务 实 例 生 成 一 个 日 志 文 件 。 < / h3 >
< / li >
< / ul >
< ul >
< li > < h3 id = "fileappender实现如下:" > FileAppender实 现 如 下 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_12.png" alt = "PNG" > < / p >
< h4 id = "以流程定义id流程实例id任务实例idlog的形式生成日志。" > 以 … /流 程 定 义 id/流 程 实 例 id/任 务 实 例 id.log的 形 式 生 成 日 志 。 < / h4 >
< / blockquote >
< / li >
< / ul >
< ul >
< li > < h3 id = "过滤匹配以taskloginfo开始的线程名称:" > 过 滤 匹 配 以 TaskLogInfo开 始 的 线 程 名 称 : < / h3 >
< blockquote >
< p > < img src = "../ ../../images/esr_13.png" alt = "PNG" > < / p >
< / blockquote >
< / li >
< / ul >
< h2 id = "总结" > 总 结 < / h2 >
< blockquote >
< h3 id = "本文从调度出发,介绍了易观研发的大数据分布式工作流调度系统。easyscheduler在易观数据平台起着中流砥柱的作用。本章着重介绍了easyscheduler的架构原理及实现思路。" > 本 文 从 调 度 出 发 , 介 绍 了 易 观 研 发 的 大 数 据 分 布 式 工 作 流 调 度 系 统 。 EasyScheduler在 易 观 数 据 平 台 起 着 中 流 砥 柱 的 作 用 。 本 章 着 重 介 绍 了 EasyScheduler的 架 构 原 理 及 实 现 思 路 。 < / h3 >
< / blockquote >
< / section >
@ -966,7 +966,7 @@
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
@ -993,46 +993,45 @@
< / div >
< script src = "../gitbook/gitbook.js" > < / script >
< script src = "../gitbook/theme.js" > < / script >
< script src = "../gitbook/gitbook.js" > < / script >
< script src = "../gitbook/theme.js" > < / script >
< script src = "../gitbook/gitbook-plugin-expandable-chapters/expandable-chapters.js" > < / script >
< script src = "../gitbook/gitbook-plugin-expandable-chapters/expandable-chapters.js" > < / script >
< script src = "../gitbook/gitbook-plugin-insert-logo-link/plugin.js" > < / script >
< script src = "../gitbook/gitbook-plugin-insert-logo-link/plugin.js" > < / script >
< script src = "../gitbook/gitbook-plugin-livereload/plugin.js" > < / script >
< script src = "../gitbook/gitbook-plugin-livereload/plugin.js" > < / script >
< script src = "../gitbook/gitbook-plugin-search/search-engine.js" > < / script >
< script src = "../gitbook/gitbook-plugin-search/search-engine.js" > < / script >
< script src = "../gitbook/gitbook-plugin-search/search.js" > < / script >
< script src = "../gitbook/gitbook-plugin-search/search.js" > < / script >
< script src = "../gitbook/gitbook-plugin-lunr/lunr.min.js" > < / script >
< script src = "../gitbook/gitbook-plugin-lunr/lunr.min.js" > < / script >
< script src = "../gitbook/gitbook-plugin-lunr/search-lunr.js" > < / script >
< script src = "../gitbook/gitbook-plugin-lunr/search-lunr.js" > < / script >
< script src = "../gitbook/gitbook-plugin-sharing/buttons.js" > < / script >
< script src = "../gitbook/gitbook-plugin-sharing/buttons.js" > < / script >
< script src = "../gitbook/gitbook-plugin-fontsettings/fontsettings.js" > < / script >
< script src = "../gitbook/gitbook-plugin-fontsettings/fontsettings.js" > < / script >
< / body >
< / body >
< / html >