本文适合有 Java 基础知识的人群

作者:HelloGitHub-Salieri

HelloGitHub 推出的《讲解开源项目》系列。讲解 PowerJob 系列即将接近尾声,本系列的干货你还喜欢吗?欢迎留言说下你的感受和后面想看的内容。

项目地址:

https://github.com/KFCFans/PowerJob

一、MapReduce 概念介绍

MapReduce 是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念 Map(映射)和 Reduce(归约),是它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。它极大地方便了编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统上。当前的软件实现是指定一个 Map(映射)函数,用来把一组键值对映射成一组新的键值对,指定并发的 Reduce(归约)函数,用来保证所有映射的键值对中的每一个共享相同的键组。

以上这一大段不算难懂的文字就是 MapReduce 的官方概念,从“大规模数据集”这个关键字可以看出,MapReduce 是面向大数据处理领域设计的,是分治思想的一种经典实现,简单概括下就是把一大坨数据通过 Map 方法切分为较小的、单机能够处理的数据块进行处理(shuffle),处理完成后通过 Reduce 方法汇总结果,具体流程如下图所示。

二、需求背景

PowerJob 作为任务调度中间件,核心职责是负责任务的调度。而 MapReduce 作为一个大数据处理模型,核心功能是大规模数据的并行处理。从表象看,PowerJob 和 MapReduce 纯属八杆子打不着的关系~相信很多人第一眼看到 PowerJob 和 MapReduce 这两个关键词一起出现时,都会有以下心理活动:

“你一个任务调度框架咋就硬要扯上 MapReduce 那么高端的概念呢?就硬蹭呗?”

其实这个问题,换个角度来思考,就能找到答案。

一般来讲,需要定时调度执行的都是离线数据同步任务,对于一些有一定体量的业务来说,这个离线数据规模可能很大,单机无法很好的完成计算。为了解决这个问题,目前市面上的调度框架普遍支持静态分片这种相对比较简陋的方式来完成分布式计算,即通过指定分片数量来调动固定数量的机器执行固定区间的任务。但很显然,这种方式非常不灵活,局限性也非常大。

那么如何实现复杂且庞大任务的分布式计算呢?阿里巴巴的 SchedulerX 团队给出了 MapReduce 这样的答案。通过自己编程的形式,实现 Map 方法,完成任务的切分,再通过 Reduce 汇总子任务结果,即可完成高度可定制的分布式计算。

PowerJob 的 MapReduce 实现便是借鉴了这一先进的思想,这里再次感谢 SchedulerX 团队~

三、示例用法

在 PowerJob 中,MapReduce 不再是高高在上、难以触碰的概念。得益于强大的底层实现和优雅的 API 设计,开发者仅需要寥寥数行代码便可完成大型任务的分布式计算,具体示例如下。

对于有分布式计算需求的任务,我们需要继承特定的抽象类 MapReduceProcessor 来开启分布式计算能力,该接口要求开发者实现两个方法,分别是 processreduce。前者负责任务的具体执行,后者负责汇集所有子任务得出具体的结果。同时,该抽象类默认提供两个可用方法:isRootTaskmap。通过调用 isRootTask 方法可以判断出当前 Task 是否为根任务,如果是根任务,则进行任务的切分(PowerJob 支持任意级 map,并不只有在根任务才能切分任务),然后调用 map 方法分发子任务。

下面放一段简单的代码示例帮助大家理解。下面这段代码模拟了目前市面上主流的“静态分片”式分布式处理,即通过控制台指定分片数量和参数(比如分3片,分片参数为:1=a&2=b&3=c)来控制参与计算的机器数量和起始参数。虽然是“杀鸡焉用牛刀”的示例,不过还是能帮助大家很好理解 PowerJob MapReduce 处理器的强大之处!

首先,我们通过 context 的 getJobParams 方法获取控制台配置的参数,即分片参数 1=a&2=b&3=c。这个分片参数代表现在需要有 3 台机器参与执行,每台机器上子任务的起始参数分别为 a、b、c。因此,我们可以根据该规则创建子任务对象 SubTask,传入分片索引 index 和 分片参数 params。

完成子任务的切分后,即可调用 map 方法完成任务的分发。

分发后该子任务会再次进入 process 方法,只不过本次是以 SubTask 而不是 RootTask 的身份进入。我们可以通过 context.getSubTask() 方法获取之前 map 出去的对象,该方法的返回值是 Object,因此我们需要使用 Java instaneof 关键字判断类型(当然,如果没有多级 map,那么该对象只可能是 SubTask 类型,直接强转即可),如果该对象为 SubTask 类型,即进行了子任务处理阶段,开始编写子任务处理逻辑即可。

当所有子任务执行完毕后,PowerJob 会调用 reduce 方法,传入所有子任务的运行结果,便于开发者构建该任务的最终结果。

@Component
public class StaticSliceProcessor extends MapReduceProcessor { @Override
public ProcessResult process(TaskContext context) throws Exception {
OmsLogger omsLogger = context.getOmsLogger(); // root task 负责分发任务
if (isRootTask()) {
// 从控制台传递分片参数,假设格式为KV:1=a&2=b&3=c
String jobParams = context.getJobParams();
Map<String, String> paramsMap = Splitter.on("&").withKeyValueSeparator("=").split(jobParams); List<SubTask> subTasks = Lists.newLinkedList();
paramsMap.forEach((k, v) -> subTasks.add(new SubTask(Integer.parseInt(k), v)));
return map(subTasks, "SLICE_TASK");
} Object subTask = context.getSubTask();
if (subTask instanceof SubTask) {
// 实际处理
// 当然,如果觉得 subTask 还是很大,也可以继续分发哦 return new ProcessResult(true, "subTask:" + ((SubTask) subTask).getIndex() + " process successfully");
}
return new ProcessResult(false, "UNKNOWN BUG");
} @Override
public ProcessResult reduce(TaskContext context, List<TaskResult> taskResults) {
// 按需求做一些统计工作... 不需要的话,直接使用 Map 处理器即可
return new ProcessResult(true, "xxxx");
} @Getter
@NoArgsConstructor
@AllArgsConstructor
private static class SubTask {
private int index;
private String params;
}
}

四、原理实现

PowerJob 的 MapReduce 思想主要来源于《Schedulerx2.0 分布式计算原理&最佳实践》这篇文章。

出于功能职责的划分(powerjob-server 仅负责任务的调度和运维),整个 MapReduce 任务的计算由执行器 powerjob-worker 自主完成。

为了便于模型的设计和功能的划分,PowerJob 为执行器节点分配了 3 种角色,分别是 TaskTracker、ProcessorTracker 和 Processor。

  • TaskTracker 是每一个任务的主节点,充当集群中的 master 角色,因此每个任务每次只会产生一个 TaskTracker。它负责子任务的分发、状态监控和集群中各执行节点的健康检查,并定期将任务的运行时信息上报给 server。
  • ProcessorTracker 是每一个执行器节点中负责执行器管理的角色,每个任务在每个执行器节点(JVM 实例)上都会产生一个 ProcessorTracker。它负责管理执行器节点任务的执行,包括接受来自 TaskTracker 的任务、上报本机任务执行情况和执行状态等功能。
  • Processor 是每一个执行器节点中负责具体执行任务的角色,也就是真正的执行单元,每个任务在每个执行器节点都会生成若干个 Processor(没错!就是控制台“实例并发数”所决定的数量)。它接受来自 ProcessorTracker 派发的执行任务并完成计算。

当需要执行分布式任务时,powerjob-server 会根据集群中各个 worker 节点的内存占用、CPU 使用率和磁盘使用率进行健康度计算,得分最高的节点将作为本次任务的 master 节点,即承担 TaskTracker 的职责。TaskTracker 在接收到来自 server 的任务执行请求时被创建,并完成三个阶段的初始化:

  • 首先需要初始化内嵌的 H2 数据库,用于存储所有子任务的派发情况和执行情况。
  • 存储就位后,TaskTracker 会根据 server 派发下来的任务内容,构建根任务,并将其持久化到内嵌数据库。
  • 最后 TaskTracker 会创建一系列定时任务,包括子任务定时派发、子任务执行状态检查、worker 健康度检查和任务整体执行状态上报。

ProcessorTracker 在接收到来自 TaskTracker 的子任务执行请求时被创建,并根据请求中携带的任务信息构建出执行所需要的线程池和对应的处理器。当子任务的运行状态发生变更后,ProcessorTracker 需要及时将最新状态反馈给 TaskTracker。

至于 Processor,本质上就是封装了每个子任务上下文信息的线程,由 ProcessorTracker 提交到执行线程池进行执行,并向上级汇报自己的执行状态。

上图清晰地展示了 PowerJob MapReduce 的工作原理,由于 MapReduce 确实算得上是非常复杂和精妙的实现,一篇文章的篇幅肯定是无法将细节说的一清二楚的。因此本文偏向于整体上的介绍,为大家讲述核心组件的划分依据和主要功能。如果对具体的细节有兴趣,那么源码是最好的资料~在本文的指导下,我个人认为花不了一天时间就能差不多看懂~

五、最后

好了,以上就是本文的全部内容了~也是 PowerJob 技术专栏的收官之作。本来打算在这里写点“离别感言”,讲一下自己这一路的心路历程,不过越写越长,都快赶上正文的篇幅了...so~就偷偷放到下期吧~


关注 HelloGitHub 公众号

PowerJob 应对庞大任务的锦囊妙计:MapReduce的更多相关文章

  1. RabbitMQ集群和失败处理

    RabbitMQ内建集群的设计用于完成两个目标:允许消费者和生产者在RabbitMQ节点在奔溃的情况下继续运行,以及通过添加更多的节点来线性扩展消息通信的吞吐量.当失去一个RabbitMQ节点时客户端 ...

  2. kafka为什么这么优秀!

    kafka为什么这么优秀! 阿飞的博客 匠心零度 今天 1.动机2.持久化3.效率4.生产者4.1负载均衡4.2异步发送5.消费者Push vs. Pull消费者位置离线数据加载 1.动机 kafka ...

  3. 微信小程序看上去很美

    目前不少关于 微信小程序 的文章主要集中在两各方面:一是开发技术细节:二是怎么靠此赚钱. -- “微信小程序”所处的环境 -- 2016年初,美国号召全民学编程,包括监狱服刑人员.同样,在中国要想掌握 ...

  4. 在CentOS上使用Nginx和Tomcat搭建高可用高并发网站

    目录 目录 前言 创建CentOS虚拟机 安装Nginx 安装Tomcat 安装lvs和keepalived 反向代理 部署网站 搭建数据库 编写网站项目 解决session一致性 注意 参考资料 前 ...

  5. 腾讯云Redis混合存储版重磅推出,万字长文助你破解缓存难题!

    导语 | 缓存+存储的系统架构是目前常见的系统架构,缓存层负责加速访问,存储层负责存储数据.这样的架构需要业务层或者是中间件去实现缓存和存储的双写.冷热数据的交换,同时还面临着缓存失效.缓存刷脏.数据 ...

  6. NGK——解决区块链用户之“难”

    自比特币诞生以来,区块链行业已发展十余年,而且在在金融.民生.司法存证.供应链协同.税务发票.版权保护等领域得到一定程度的应用,但大多属于边缘业务,以探索试点为主,应用深度和广度不足.为什么会这样?是 ...

  7. Hadoop阅读笔记(一)——强大的MapReduce

    前言:来园子已经有8个月了,当初入园凭着满腔热血和一脑门子冲动,给自己起了个响亮的旗号“大数据 小世界”,顿时有了种世界都是我的,世界都在我手中的赶脚.可是......时光飞逝,岁月如梭~~~随手一翻 ...

  8. 使用MapReduce实现一些经典的案例

    在工作中,很多时候都是用hive或pig来自动化执行mr统计,但是我们不能忘记原始的mr.本文记录了一些通过mr来完成的经典的案例,有倒排索引.数据去重等,需要掌握. 一.使用mapreduce实现倒 ...

  9. [翻译]MapReduce: Simplified Data Processing on Large Clusters

    MapReduce: Simplified Data Processing on Large Clusters MapReduce:面向大型集群的简化数据处理 摘要 MapReduce既是一种编程模型 ...

随机推荐

  1. 浅谈python深复制与浅复制区别

    话不多说,看代码

  2. GaussDB连接与登出

    连接 连接命令1: gsql -d ${dbName} -U ${userName} -p {port:默认为25308} -h {ip} -W {password} 连接命令2: gsql -d p ...

  3. 面试官最爱的 volatile 关键字,这些问题你都搞懂了没?

    前言 volatile相关的知识点,在面试过程中,属于基础问题,是必须要掌握的知识点,如果回答不上来会严重扣分的哦. volatile关键字基本介绍 volatile可以看成是synchronized ...

  4. JAVA 读取excel文件成List<Entity>

    package com.fsinfo.common.utils; import com.fsinfo.modules.enterprise.entity.EnterpriseRecordEntity; ...

  5. setjmp()/longjmp()的使用方法

    setjmp和longjmp.为了让你实现复杂的流控制,程序在系统里面运行完全依靠内存(代码段,全局段,堆存储器,栈存储器)和寄存器的内容(栈指针,基地址,计数器),setjmp保存当前的寄存器里面的 ...

  6. “程序包com.sun.org.apache.xml.internal.security.utils不存在”的问题

    方法一(eclipse): 网上大神的回答: 自己写的程序是不建议用com.sun这个玩意儿的..这东西属于“Deprecated and restricted API”.. 而且各种com.sun的 ...

  7. Istio 网络弹性 实践 之 故障注入 和 调用超时

    网络弹性介绍 网络弹性也称为运维弹性,是指网络在遇到灾难事件时快速恢复和继续运行的能力.灾难事件的范畴很广泛,比如长时间停电.网络设备故障.恶意入侵等. 超时时间 工作中常常会碰到这样的开发.测试场景 ...

  8. 获取 python 包的路径

    root@ostack01:~# python Python 2.7. (default, Nov , ::) [GCC 5.4. ] on linux2 Type "help", ...

  9. 第4篇scrum冲刺(5.24)

    一.站立会议 1.照片 2.工作安排 成员 昨天已完成的工作 今天的工作安排 困难 陈芝敏  完成云开发配置,初始化数据库:  线下模块(还剩下获取词的数据库) 倒计时模块的初加载还是有点慢  冯晓凤 ...

  10. ElementUI 不维护了?供我们选择的 Vue 组件库还有很多!

    前文回顾:Vue+Spring Boot 前后端分离的商城项目开源啦! Vue 组件千千万,只要不行咱就换. ElementUI 近况 根据我最近的观察,得知一些关于 ElementUI 维护人员都退 ...