TbSchedule任务调度管理框架的整合部署

一、前言

任务调度管理作为基础架构通常会出现于我们的业务系统中,目的是让各种任务能够按计划有序执行。比如定时给用户发送邮件、将数据表中的数据同步到另一个数据表都是一个任务,这些相对耗时的操作通过任务调度系统来异步并行执行,既能提高任务的执行效率又能保障任务执行的可靠性。

实现的方式也是多种多样,比如使用Timer进行简单调度或者使用Quartz类似的框架,本文基于淘宝开源框架TbSchedule实现,其设计目的是让批量任务或者不断变化的任务能够被动态的分配到多个主机的JVM中,在不同的线程组中并行执行,所有的任务能够被不重复,不遗漏的快速处理,目前被应用于阿里巴巴众多业务系统。

请参照http://code.taobao.org/p/tbschedule/wiki/index/,相关内容不再重复介绍,本文记录了详细的部署整合操作步骤。

最新源码地址已经迁移到:https://github.com/nmyphp/tbschedule

 

二、Zookeeper部署

1、TbSchedule依赖于Zookeeper,实现任务的分布式配置及各服务间的交互通信,Zookeeper以TreeNode类型进行存储,支持Cluster形式部署且保证最终数据一致性,关于ZK的资料网上比较丰富,相关概念不再重复介绍,本文以zookeeper-3.4.6为例,请从官网下载http://zookeeper.apache.org

2、创建ZookeeperLab文件夹目录,模拟部署3台Zookeeper服务器集群,目录结构如下。

     

3、解压从官网下载的zookeeper-3.4.6.tar文件,并分别复制到三台ZkServer的zookeeper-3.4.6文件夹。

     

4、分别在三台ZkServer的data目录下创建myid文件(注意没有后缀),用于标识每台Server的ID,在Server1\data\myid文件中保存单个数字1,Server2的myid文件保存2,Server3的myid保存3。

5、创建ZkServer的配置文件,在zookeeper-3.4.6\conf文件夹目录下创建zoo.cfg,可以从示例的zoo_sample.cfg 复制重命名。因为在同一台机器模拟Cluster部署,端口号不能重复,配置文件中已经有详细的解释,修改后的配置如下,其中Server1端口号2181,Server2端口号2182,Server3端口号2183。

 View Code

6、通过zookeeper-3.4.6\bin文件夹zkServer.bat文件启动ZKServer,由于Cluster部署需要选举Leader和Followers,所以在3个ZKServer全部启动之前会提示一个WARN,属正常现象。

      

7、Zookeeper启动成功后可以通过zookeeper-3.4.6\bin文件夹的 zkCli.bat验证连接是否正常,比如创建节点“create /testnode helloworld”,查看节点“get /testnode”,连接到组群中其它ZkServer,节点数据应该是一致的。更多指令请使用help命令查看。

     

8、对于Linux环境下部署基本一致,zoo.cfg配置文件中data和datalog文件夹路径改为linux格式路径,使用“./zkServer.sh start-foreground”命令启动ZkServer,注意start启动参数不能输出异常信息。

      

9、至此Zookeeper的配置完毕。

 

三、SVN从TaoCode获取并Import TbSchedule源码(请先配置Maven环境)

 

四、TbSchedule控制台的部署

1、TbSchedule Console是基于web页面对调度任务配置、部署、监控的终端。

2、将源码目录下的console\ScheduleConsole.war包及depend-lib库部署到Web应用服务器,本文以Tomcat 7.0.54为例,相关步骤不再描述。

3、首次打开页面会跳转到“基础信息配置”Config.jsp页面,其中前2项为Zookeeper服务的连接配置,请正确填写前面部署的Zookeeper服务器地址和端口号,多个ZKServer可以使用逗号分隔。后3项是用于标识ZkServer中调度任务配置的根节点和节点权限,无严格要求。

     

 4、点击保存后,会提示 “错误信息:Zookeeper connecting ......localhost:2181”,如果ZKServer配置正确可以不用理会,直接点击“管理主页”,若不能正常跳转到Index.jsp页面请重新检查Zookeeper的配置,建议关闭防火墙。

     

5、TbSchedule Console Web站点对应的两个地址

     [监控页面]       http://localhost:8081/ScheduleConsole/schedule/index.jsp

     [管理页面]       http://localhost:8081/ScheduleConsole/schedule/index.jsp?manager=true

     如果以上地址能正常访问则TbSchedule Console的部署配置完成。

 

五、Task场景设计

1、假设场景:任务需要将订单表tbOrder中制单日期在20141201--20141208內(共8天)的数据同步到备份tbOrder_copy 表,其中每2天分为一个任务组并行同步(每次提取500条),关于任务组的划分和TbSchedule中TaskItem的相关概念请先参考wiki,后续也会有部分解释。

2、数据环境使用MySql数据库,创建tbOrder和tbOrder_Copy数据表,结构相同,同时在tbOrder事先生成好测试数据,建议每天的数据量在1000条以上。

      

     

 

六、数据同步任务实现

1、创建TaskCenter Demo Project,添加对tbschedule project的依赖,并集成Spring Framework。

     

2、创建OrderInfo实体类,属性对应tbOrder表,用于映射从数据表取的数据。

3、创建DataSyncABean任务类,实现IScheduleTaskDealSingle<T>泛型接口的selectTasks和execute方法,其中selectTasks方法用于取数,execute方法用于执行selectTasks返回的Result,关于代码中任务片段的划分和TbSchedule中TaskItem的相关概念后续再解释,代码参考如下。

 View Code

4、在Spring容器中注册数据同步任务Bean。

 View Code

5、Main函数初始化Spring容器和TbSchedule 任务管理Factory,连接ZKServer,代码如下,也可以参照TbSchedule源码中通过Spring进行初始化TBScheduleManagerFactory 。

 View Code

6、如果配置正确应该可以成功启动该TaskDeal服务。

 

七、在TbSchedule Console创建调度任务(请事先仔细阅读wiki中的概念解释)

1、创建调度策略,其中“最大线程组数量”设置为4,表示在机器上的通过4个线程组并行执行数据同步任务。

     

2、创建调度任务

     任务名称:对应调度策略中的任务名称,标识任务和策略的关联关系;

     任务处理的SpringBean:对应Demo TaskDeal服务Spring容器中的任务对象ID;

 View Code

     每次获取数据量:对应于bean任务类selectTasks方法参数 eachFetchDataNum;

     执行开始时间:“0 * * * * ?” 表示每分钟的0秒开始,表达式同Quartz设置的Crontab格式,有工具可以生成,详细解释参照http://dogstar.iteye.com/blog/116130

 View Code

     任务项:前面假设的任务场景中需要把1-8号数据按2天为1个任务组并行数据同步,所以可以把任务划分为1,2,3,4,5,6,7,8一共8个任务碎片,8个任务碎片被分配到4个线程组,那么每个线程组对应2个任务碎片,运行时任务项参数又被传递到bean任务类selectTasks方法的List<TaskItemDefine> queryCondition参数,例如第1个线程组调用selectTasks方法是queryCondition参数条件为1,2 ,第2个线程组执行参数条件为3,4,bean任务类中根据参数生成对应的BuildDate条件取数,并将结果提交到execute方法执行,从而实现并行计算。

     任务项的划分:可以有非常多的分配策略和技巧,例如将一个数据表中所有数据的ID按10取模,就将数据划分成了0、1、2、3、4、5、6、7、8、9供10个任务项;将一个目录下的所有文件按文件名称的首字母(不区分大小写),就划分成了A、B、C、D、E、F、G、H、I、J、K、L、M、N、O、P、Q、R、S、T、U、V、W、X、Y、Z供26个任务项 。

     

3、新增任务配置保存后,Task会按指定的时间段自动开始执行,可以从TbScheduleConsole监视到对应的线程组和任务项。

     

4、同时可以从任务TaskDeal服务控制台监视到Debug输出的取数SQL和 Insert语句,以及TbSchedule的Heartbeat、TaskItem Debug信息。

 View Code

      

     

5、至此同步任务配置完成,并且实现了模拟场景的要求。

 

 

TBSchedule是什么

TBSchedule是一个支持分布式的调度框架,让批量任务或者不断变化的任务能够被动态的分配到多个主机的JVM中,在不同的线程组中并行执行,所有的任务能够被不重复,不遗漏的快速处理。基于ZooKeeper的纯Java实现,由Alibaba开源。    

TBSchedule能干什么

TBSchedule可以将调度作业从业务系统中分离出来,降低或者是消除和业务系统的耦合度,进行高效异步任务处理。在互联网和电商领域TBSchedule的使用非常广泛,目前被应用于阿里巴巴、淘宝、支付宝、京东、聚美、汽车之家、国美等很多互联网企业的流程调度系统。

TBSchedule如何使用

1、下载TBSchdule源码

下载地址:TBSchedule源码

官网目前打不开了,可从本人的百度网盘下载。链接:TBSchedule源码 密码:vuzg

2、部署zookeeper

下载zookeeper并安装部署。ZK下载地址

3、部署管理控制台

把下载的源码的console文件夹中ScheduleConsole.war文件部署到tomcat容器中,并启动tomcat服务。

 
图1.管理控制台war包

访问http://localhost:8080/ScheduleConsole/schedule/config.jsp地址进行链接ZK的基础信息配置,如下图:

 
图2.配置ZK连接信息

4、编写客户端代码

引入jar包

<dependency>

    <groupId>com.taobao.pamirs.schedule</groupId>

    <artifactId>tbschedule</artifactId>

    <version>3.3.3.2</version>

</dependency>

配置zk连接信息

<bean id="scheduleManagerFactory" class="com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory"

      init-method="init">

    <property name="zkConfig">

 

            <entry key="zkConnectString" value="localhost:2181" />

            <entry key="rootPath" value="/tb-schedule/test" />

            <entry key="zkSessionTimeout" value="60000" />

            <entry key="userName" value="admin" />

            <entry key="password" value="123456" />

            <entry key="isCheckParentPath" value="true" />

</bean>

实现IScheduleTaskDealSingle接口及selectTasks()方法和execute()方法。

package com.zhl.tbSchedule;

import com.alibaba.fastjson.JSONObject;

import com.taobao.pamirs.schedule.IScheduleTaskDealSingle;

import com.taobao.pamirs.schedule.TaskItemDefine;

import com.zhl.tbSchedule.business.dao.TBOrderMapper;

import com.zhl.tbSchedule.business.dao.TBOrderMapperCopy;

import com.zhl.tbSchedule.business.domain.TBOrder;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import java.util.ArrayList;

import java.util.Comparator;

import java.util.Date;

import java.util.List;

@Slf4j

@Component("iScheduleTaskDealSingleTest")

public class IScheduleTaskDealSingleTestimplements IScheduleTaskDealSingle {

@Autowired

    public TBOrderMappertbOrderMapper;

    @Autowired

    public TBOrderMapperCopytbOrderMapperCopy;

    @Override

    public ComparatorgetComparator() {

return null;

    }

@Override

    public ListselectTasks(String taskParameter, String ownSign, int taskQueueNum,

                                    List taskItemList, int eachFetchDataNum)throws Exception {

System.out.println(Thread.currentThread().getName() +" _IScheduleTaskDealSingleTest start to selectTasks..........");

        if (taskItemList ==null || taskItemList.size() <0) {

return null;

        }

System.out.println("IScheduleTaskDealSingleTest config params,taskParameter:{" + taskParameter +"},ownSina:{"

                + ownSign +"},taskQueueNum:{" + taskQueueNum +"},taskItemList:{" + JSONObject.toJSONString(taskItemList)

+"}, eachFetchDataNum:{" + eachFetchDataNum +"}");

        List models =new ArrayList();

        String billingNumber ="";

        for (TaskItemDefine taskItemDefine : taskItemList) {

billingNumber += taskItemDefine.getTaskItemId() +"";

        }

if (StringUtils.isNotBlank(billingNumber)) {

//billingNumber = billingNumber.substring(0,billingNumber.length() - 1);

            models =tbOrderMapper.selectByBillNumber(billingNumber, eachFetchDataNum);

        }

System.out.println("IScheduleTaskDealSingleTest selectTasks result..........models.size:" + models.size());

        return models;

    }

@Override

    public boolean execute(TBOrder model, String ownSign)throws Exception {

System.out.println(Thread.currentThread().getName() +" _IScheduleTaskDealSingleTest执行开始啦.........." +new Date());

        // System.out.println(model);

        tbOrderMapperCopy.insertTBOrder(model);

        tbOrderMapper.updateStatus(model.getBillNumber());

return true;

    }

}

selectTasks方法参数说明:

taskParameter:对应控制台自定义参数,可自定义传入做逻辑上的操作

taskQueueNum:对应控制台任务项数量

taskItemList:集合中TaskItemDefine的id值对应任务项值,多线程处理时,根据任务项协调数据一致性和完整性

eachFetchDataNum:对应控制台每次获取数量,由于子计时单元开始后,会不断的去取数据进行处理,直到取不到数据子计时才停止,等待下一个子计时开始。可以限制每次取数,防止一次性数据记录过大,内存不足。

ownSign:环境参数,可用于区分生产、测试、开发环境

5、配置任务管理和调度策略

 
图3.任务管理配置

配置参数说明:

任务名称:策略调度的标示,一旦创建保存,不可更改

任务处理的SpringBean:注册到spring的任务bean,如iScheduleTaskDealSingleTest

心跳频率/假定服务死亡时间/处理模式/没有数据时休眠时长/执行结束时间:一般保持默认即可

线程数:处理该任务的线程数(一个线程组的线程数量),在没有划分多任务项的情况下,多线程是没有意义的,且线程数量大于任务项也是没有意义的(线程数小于等于任务项),注意如果开启多线程,必须对数据做任务项过滤

单线程组最大任务项:配置单JVM处理的最大任务项数量,多任务项情况下,可按需限制,一般默认,多执行机会均衡分配

每次获取数量:子计时单元开始,线程会不断的去获取数据(selectTasks方法每次获取的限制)并处理数据,直到获取不到数据子计时才结束(方法内不用就可以随意配置)

每次执行数量:每次execute方法执行的数据量,只在bean实现IScheduleTaskDealMulti才生效

每次处理完休眠时间:子计时单元开始,只要有数据,就会不停的获取不停的处理,这个时间设置后,子计时单元开始每次获取执行后,不管还有没有数据,都先歇会儿再获取处理

自定义参数:可自定义控制任务逻辑操作

任务项:这项很重要,在多线程情况下,划分任务项是有意义的,但是要注意必须通过任务项参数,协调待处理数据

 
图4.调度策略配置

配置参数说明:

策略名称:策略标示,可任意填写

任务类型:一般保持默认Schedule

任务名称:对应任务栏被调度任务名称

任务参数:一般不用,保持默认

单JVM最大线程组数量:单个JVM允许开启的线程组数

最大线程组数量:多处理机情况下的线程总数限制(总线程为2,任务项线程为4是没有意义的)

IP地址:127.0.0.1或者localhost会在所有机器上运行,注意多处理机若没有根据任务子项划分数据处理,会导致多处理机重复处理数据,谨慎配置

配置完成后启动客户端即可进行任务调度。

6、分布式高可用高效率保障

1)调度机的高可用有保障

多调度机向注册中心注册后,共享调度任务,且同一调度任务仅由一台调度机执行调度,当前调度机异常宕机后,其余的调度机会接上

2)执行机的高可用有保障

多执行机向注册中心注册后,配置执行机单线程(多机总线程为1)执行任务,调度机会随机启动一台执行机执行,当前执行异常机宕机后,调度机会会新调度一台执行机。

3)执行机的并行高效保障

配置执行机多线程且划分多任务子项后,各任务子项均衡分配到所有执行机,各执行机均执行,多线程数据一致性协调由任务项参数区分。

4)弹性扩展失效转移保障

运行中的执行机宕机,或新增执行机,调度机将在下次任务执行前重新分配任务项,不影响正常执行机任务(崩溃的执行机当前任务处理失效);运行中的调度机宕机或动态新增调度机,不影响执行机当前任务,调度机宕机后动态切换。



posted @ 2022-02-16 09:29  hanease  阅读(501)  评论(0编辑  收藏  举报