前言
先来讲解下在分布式任务调度SchedulerX中的MapReduce模型是什么,从字面来理解MapReduce就是一个任务切分多个子任务并行处理。在简单单机场景下就是开启多线程来同时处理一个大任务,在多个机器下可以有多台机器同时并行处理同一个任务,分布式调度的MapReduce模型就是为使用者在代码开发层面屏蔽上述并行协调的逻辑,让使用者可以通过简单的业务逻辑开发完成并行任务设计开发。该模型场景与很多批处理框架的处理模型有其相似及可替代之处。
MapReduce模型结构
批处理框架对比
常见的Spring Batch批处理框架,其提供的批处理能力与分布式任务调度中的MapReduce能力有着相似的功能。Spring Batch任务批处理框主要提供:单进程多线程处理、分布式多进程处理两种方式。在单进程多线程处理模式下,用户可自行定一个Job作为一个批处理任务单元,在Job是由一个或多个Step步骤进行串联或并行组成,每一个Step又分别由reader、process、writer构成来完成每一步任务的读取、处理、输出。
对于Spring Batch框架个人觉得单进程下多线程实践意义并不是太大,主要是如果在较小批量数据任务处理采用该框架来做有点费功夫,完全可以自行开线程池来解决问题;那么在数据量较大的批处理处理场景下,单进程的处理能力显然是存在处理能力扩展瓶颈的。因此数据批处理场景下可能分布式多进程远程协调处理能力更具有实践意义。在Spring Batch中提供了远程分片/分区处理能力,将某一个Step中过根据特定的规则将任务拆分成多个子任务并分发给集群中其他的worker来处理,以实现远程MapReduce能力。其相关的远程交互能力常见是借助第三方消息中间件来完成子任务分发和执行结果汇聚。
优劣分析
SchedulerX的MapReduce与Spring Batch框架,综合比较结果:
- 批处理能力:两者都支持单机多线程、分布式多进程处理,Spring Batch在批处理细节能力完备,SchdulerX MapReduce定义更简洁。
- 定时调度:SchdulerX本身自带定时调度能力;Spring Batch无该能力需集成三方定时框架。
- 管控能力:SchdulerX具备任务可视化管控能力;Spring Batch常见采用程序或文件配置任务,管控台需额外搭建且能力较弱。
- 集成难度:Spring Batch开源框架集成,分布式并行能力需额外第三方中间件集成搭建;SchdulerX集成SDK自动对接阿里云公有云平台,无需额外组件实现分布式MapReduce弹性能力。
开发MapReduce任务
下面讲解如果在任务调度平台上创建MapReduce任务,对于业务应用开发者而言主要是完成下面的步骤:
1、注册应用
任务调度平台上创建应用分组,构建一个属于自己业务应用的服务接入点
2、集成对接
在自己的业务应用程序中添加SDK集成依赖,配置相应的应用分组接入信息,部署发布应用集群即可。在控制台->应用管理实例信息中即可查看应用节点的对接信息。
<dependency>
<groupId>com.alibaba.schedulerx</groupId>
<artifactId>schedulerx2-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
# 命名空间ID
spring.schedulerx2.namespace=********-ba41-d40c7153c838
# 对接平台服务地址,本地调试可采用如下“公网”;其他region详见官方文档
spring.schedulerx2.endpoint=acm.aliyun.com
# 平台上创建的应用分组id
spring.schedulerx2.groupId=********
# 平台上创建的应用分组key
spring.schedulerx2.appKey=********
3、MapReduce任务开发
开发符合自己业务场景需要MapReduce任务,主要分为两个步骤:1.子任务拆分逻辑;2.单个子任务业务处理逻辑。任务运行时在集群中的子任务分布式协调处理逻辑开发者都无需关心。相应代码开发完成后即可发布部署至应用集群。
@Component
public class ParallelJob extends MapReduceJobProcessor {
private static final Logger logger = LoggerFactory.getLogger("schedulerx");
@Override
public ProcessResult reduce(JobContext context) throws Exception {
return new ProcessResult(true);
}
@Override
public ProcessResult process(JobContext context) throws Exception {
if(isRootTask(context)){
// ---------------------------------------------------------
// Step1. 子任务的拆分逻辑开发,子任务对象可为业务自定义(内容务必简洁)
// ---------------------------------------------------------
logger.info("构建MapReduce的子任务列表...");
List<ParallelAccountInfo> list = new LinkedList();
/**
* 判断如果是rootTask的情况下,构建MapReduce子任务对象列表
* 在实际业务场景中,用户可自行根据业务场景加载子任务对象且该业务对象实现BizSubTask接口
* 场景案例:
* 1、从数据库中加载未被处理的客户账户信息
* 2、构建省份城市地区信息列表,按区域分发任务处理
* 3、根据业务标签作为子任务分类,如:电器、日用品、食品等
* 4、可根据时间作为子任务分类,如:按月(1月、2月...)
*/
for(int i=0; i < 20; i++){
list.add(new ParallelAccountInfo(i, "CUS"+StringUtils.leftPad(i+"", 4, "0"),
"AC"+StringUtils.leftPad(i+"", 12, "0")));
}
return map(list, "transfer");
}else {
// ---------------------------------------------------------
// Step2. 针对单个子任务的业务逻辑处理
// ---------------------------------------------------------
/**
* 非rootTask,用户可以获取对应的子任务信息进行相应的业务处理
*/
ParallelAccountInfo obj = (ParallelAccountInfo)context.getTask();
// 针对获取的 obj子任务信息,强制还原为业务对象进行相应业务逻辑处理
logger.info("处理子任务信息:{}", JSON.toJSONString(obj));
return new ProcessResult(true);
}
}
}
4、配置任务
针对上述开发的MapReduce任务在控制台->任务管理中配置创建相应的任务信息,实现调度平台上任务的触发运行。在高级设置中可配置单机子任务线程数量等相关参数。
5、查看执行结果
任务在执行后,在控制台->执行记录中选择对应的任务实例查看其执行详情,在子任务列表中会显示每一个拆分的子任务分发执行信息,包括:包括该子任务执行状态、执行节点、日志等。
新特性(业务标签)
在任务调度最新的专业版应用中提供了子任务业务标签功能,在基础版的子任务列表中主要面临以下几个困境:
- 某子任务执行报错或持续运行中,无法快速直观知道具体是哪个业务子任务单元处理出现了问题
- 在大量的子任务记录中,用户无法精准查询具体某个业务子任务的处理情况信息
因此MapReduce任务自定义的业务标签,主要是用于解决上述需求场景。业务如何对子任务进行自定义标签设置参考如下:
public class ParallelAccountInfo implements BizSubTask {
// 业务参数按业务场景需求自定义
private long id;
private String name;
private String accountId;
public ParallelAccountInfo(long id, String name, String accountId) {
this.id = id;
this.name = name;
this.accountId = accountId;
}
/**
* 自定义处理核心:
* 业务实体需实现BizSubTask:labelMap方法,
* 在该方法实现中设置对应子任务的标签信息,挑取子任务中核心业务信息作为lable
* @return
*/
@Override
public Map<String, String> labelMap() {
Map<String, String> labelMap = new HashMap();
labelMap.put("户名", name);
return labelMap;
}
}
MapReduce分发处理的子任务实体对象在实现上述接口逻辑后,在子任务列表中即可展现出相应的子任务标签内容信息,且可以通过模糊查询功能锁定需要查找的子任务处理情况。
运用场景
MapReduce模型或者说批处理场景,在实际企业级应用中是有大量的需求存在。一些常见的使用方式如:
- 针对分库分表数据批量并行处理,将分库或分表信息作为子任务对象在集群节点间分发实现并行处理
- 按城市区域的物流订单数据处理,将城市和区域作为子任务对象在集群节点间分发实现并行处理
- 鉴于MapReduce子任务可视化能力,可将重点客户/订单信息作为子任务处理对象,来进行相应数据报表处理或信息推送,以实现重要子任务的可视化跟踪处理
基金销售业务案例
以下提供一个基金销售业务常见下案例以供参考,以便使用者在自己的业务场景下自由发挥。案例说明:在基金公司与基金销售公司(如:蚂蚁财富)之间每天会由投资者的账户/交易申请需要进行数据同步处理,往往采用的是文件数据交互,一个基金公司对接着N多家销售商(反之亦然),每家销售商提供的数据文件完全独立;每一个销售商的数据文件都需要经过文件校验、接口文件解析、数据校验、数据导入这么几个固定步骤。销售商上述的固定处理步骤就非常适合采用批处理MapReduce方式以加快数据文件处理。
@Component
public class FileImportJob extends MapReduceJobProcessor {
private static final Logger logger = LoggerFactory.getLogger("schedulerx");
@Override
public ProcessResult reduce(JobContext context) throws Exception {
return new ProcessResult(true);
}
@Override
public ProcessResult process(JobContext context) throws Exception {
if(isRootTask(context)){
// ---------------------------------------------------------
// Step1. 读取对接的销售商列表Code
// ---------------------------------------------------------
logger.info("以销售商为维度构建子任务列表...");
// 伪代码从数据库读取销售商列表,Agency类需要实现BizSubTask接口并可将
// 销售商名称/编码作为子任务标签,以便控制台可视化跟踪
List<Agency> agencylist = getAgencyListFromDb();
return map(agencylist, "fileImport");
}else {
// ---------------------------------------------------------
// Step2. 针对单个销售商进行对应文件数据的处理
// ---------------------------------------------------------
Agency agency = (Agency)context.getTask();
File file = loadFile(agency);
logger.info("文件加载完成.");
validFile(file);
logger.info("文件校验通过.");
List<Request> request = resolveRequest(file);
logger.info("文件数据解析完成.");
List<Request> request = checkRequest(request);
logger.info("申请数据检查通过.");
importRequest(request);
logger.info("申请数据导入完成.");
return new ProcessResult(true);
}
}
}
上面描述的案例主要是将基金交易清算中的一个业务环节,采用MapReduce批处理方式来进行处理,其他后续处理的每一个环节也可以采用该方式来进行处理。后续还可以通过每一个MapReduce节点DAG依赖编辑构建一个完整的自动业务清算流程。在SchdulerX任务调度平台处理类似案例,可有效解决以下问题:
- 可通过动态弹性扩容来应对业务量增长带来的处理效率提升
- 每个销售商独立可视化处理分析,可单独针对某个销售商查看其处理状态以及其处理的业务日志信息
原文链接: