关于TbSchedule任务调度管理框架的整合部署 - lengfo
一、前言
任务调度管理作为基础架构通常会出现于我们的业务系统中,目的是让各种任务能够按计划有序执行。比如定时给用户发送邮件、将数据表中的数据同步到另一个数据表都是一个任务,这些相对耗时的操作通过任务调度系统来 异步并行执行,既能提高任务的执行效率又能保障任务执行的可靠性。
实现的方式也是多种多样,比如使用Timer进行简单调度或者使用Quartz类似的框架,本文基于淘宝开源框架TbSchedule实现,其设计目的是让批量任务或者不断变化的任务能够被动态的分配到多个主机的JVM中,在不同的线程组中并行执行,所有的任务能够被不重复,不遗漏的快速处理,目前被应用于阿里巴巴众多业务系统。
请参照 http://code.taobao.org/p/tbschedule/wiki/index/,相关内容不再重复介绍,本文记录了详细的部署整合操作步骤。
二、Zookeeper部署
1、TbSchedule依赖于 Hadoop 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。
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=E:/ZookeeperLab/server1/data
dataLogDir=E:/ZookeeperLab/server1/logs
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
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的相关概念后续再解释,代码参考如下。
2
3 import java.sql.ResultSet;
4 import java.util.ArrayList;
5 import java.util.Comparator;
6 import java.util.List;
7
8 import com.taobao.pamirs.schedule.IScheduleTaskDealSingle;
9 import com.taobao.pamirs.schedule.TaskItemDefine;
10
11 import DBHelper.*;
12
13 public class DataSyncABean implements IScheduleTaskDealSingle<OrderInfo> {
14
15 public List<OrderInfo> selectTasks(String taskParameter, String ownSign,
16 int taskItemNum, List<TaskItemDefine> queryCondition,
17 int eachFetchDataNum) throws Exception {
18
19 List<OrderInfo> result = new ArrayList<OrderInfo>();
20 if (queryCondition.size() == 0) {
21 return result;
22 }
23
24 StringBuffer condition = new StringBuffer();
25 for (int i = 0; i < queryCondition.size(); i++) {
26 if (i > 0) {
27 condition.append(",");
28 }
29 condition.append(queryCondition.get(i).getTaskItemId());
30 }
31
32 /* 场景A:将tbOrder表中的数据分8个任务项,每次取200条数据, 同步到tbOrder_copy表中。 */
33 String sql = "select * from tbOrder " + "where "
34 + " BillNumber not in (select BillNumber from tbOrder_copy) "
35 + " and RIGHT(BuildDate,1) in (" + condition + ") " + "limit "
36 + eachFetchDataNum;
37
38 System.out.println("开始执行SQL:" + sql);
39
40 ResultSet rs = MySQLHelper.executeQuery(sql);
41 while (rs.next()) {
42 OrderInfo order = new OrderInfo();
43 order.BillNumber = rs.getString("BillNumber");
44 order.BuildDate = rs.getString("BuildDate");
45 order.Customer = rs.getString("Customer");
46 order.GoodsName = rs.getString("GoodsName");
47 order.Amount = rs.getFloat("Amount");
48 order.SaleMoney = rs.getFloat("SaleMoney");
49 result.add(order);
50
51 if (rs.isLast()) {
52 break;
53 }
54 }
55 MySQLHelper.free(rs, rs.getStatement(), rs.getStatement()
56 .getConnection());
57
58 return result;
59 }
60
61 public Comparator<OrderInfo> getComparator() {
62
63 return null;
64 }
65
66 public boolean execute(OrderInfo task, String ownSign) throws Exception {
67 String sql = "insert into tbOrder_copy values('" + task.BillNumber
68 + "','" + task.BuildDate + "','" + task.Customer + "','"
69 + task.GoodsName + "'," + task.Amount + "," + task.SaleMoney
70 + ")";
71
72 MySQLHelper.executeNonQuery(sql);
73
74 System.out.println("execute:" + sql);
75
76 return true;
77 }
78 }
4、在Spring容器中注册数据同步任务Bean。
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
5
6
7 <bean id="dataSyncABean" class="Task.DataSyncABean" />
8
9 </beans>
5、Main函数初始化Spring容器和TbSchedule 任务管理Factory,连接ZKServer,代码如下,也可以参照TbSchedule源码中通过Spring进行初始化TBScheduleManagerFactory 。
2
3 import org.springframework.context.ApplicationContext;
4 import org.springframework.context.support.FileSystemXmlApplicationContext;
5
6
7 import com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory;
8
9 public class TaskCenter {
10
11 public static void main(String[] args) throws Exception {
12
13 // 初始化Spring
14 ApplicationContext ctx = new FileSystemXmlApplicationContext(
15 "src\\applicationContext.xml");
16
17 // 初始化调度工厂
18 TBScheduleManagerFactory scheduleManagerFactory = new TBScheduleManagerFactory();
19
20 Properties p = new Properties();
21 p.put("zkConnectString", "localhost:2181");
22 p.put("rootPath", "/tbSchedule/Test");
23 p.put("zkSessionTimeout", "60000");
24 p.put("userName", "zookeeper");
25 p.put("password", "zookeeper");
26 p.put("isCheckParentPath", "true");
27
28 scheduleManagerFactory.setApplicationContext(ctx);
29
30 scheduleManagerFactory.init(p);
31 }
32 }
6、如果配置正确应该可以成功启动该TaskDeal服务。
七、在TbSchedule Console创建调度任务(请事先仔细阅读wiki中的概念解释)
1、创建调度策略,其中“最大线程组数量”设置为4,表示在机器上的通过4个线程组并行执行数据同步任务。
2、创建调度任务
任务名称:对应调度策略中的任务名称,标识任务和策略的关联关系;
任务处理的SpringBean:对应Demo TaskDeal服务Spring容器中的任务对象ID;
每次获取数据量:对应于bean任务类selectTasks方法参数 eachFetchDataNum;
执行开始时间:“0 * * * * ?” 表示每分钟的0秒开始,表达式同Quartz设置的Crontab格式,有工具可以生成,详细解释参照 http://dogstar.iteye.com/blog/116130;
3.秒 0-59 , - * /
4.分 0-59 , - * /
5.小时 0-23 , - * /
6.日期 1-31 , - * ? / L W C
7.月份 1-12 或者 JAN-DEC , - * /
8.星期 1-7 或者 SUN-SAT , - * ? / L C #
9.年(可选) 留空, 1970-2099 , - * /
10.表达式意义
11."0 0 12 * * ?" 每天中午12点触发
12."0 15 10 ? * *" 每天上午10:15触发
13."0 15 10 * * ?" 每天上午10:15触发
14."0 15 10 * * ? *" 每天上午10:15触发
15."0 15 10 * * ? 2005" 2005年的每天上午10:15触发
16."0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
17."0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
18."0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
19."0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
20."0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
21."0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
22."0 15 10 15 * ?" 每月15日上午10:15触发
23."0 15 10 L * ?" 每月最后一日的上午10:15触发
24."0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
25."0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
26."0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
27.每天早上6点
28.0 6 * * *
29.每两个小时
30.0 */2 * * *
31.晚上11点到早上8点之间每两个小时,早上八点
32.0 23-7/2,8 * * *
33.每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点
34.0 11 4 * 1-3
35.1月1日早上4点
36.0 4 1 1 *
任务项:前面假设的任务场景中需要把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信息。
2 17:01:00.000 [DataSyncATask-2-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManager - 恢复调度:DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001
3 17:01:00.000 [DataSyncATask-3-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManager - 恢复调度:DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002
4 17:01:00.002 [DataSyncATask-0-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManager - 恢复调度:DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000
5 开始执行SQL:select * from tbOrder where BillNumber not in (select BillNumber from tbOrder_copy) and RIGHT(BuildDate,1) in (7,8) limit 500
6 开始执行SQL:select * from tbOrder where BillNumber not in (select BillNumber from tbOrder_copy) and RIGHT(BuildDate,1) in (5,6) limit 500
7 开始执行SQL:select * from tbOrder where BillNumber not in (select BillNumber from tbOrder_copy) and RIGHT(BuildDate,1) in (3,4) limit 500
8 开始执行SQL:select * from tbOrder where BillNumber not in (select BillNumber from tbOrder_copy) and RIGHT(BuildDate,1) in (1,2) limit 500
9 17:01:00.289 [DataSyncATask-4-exe3] DEBUG c.t.p.s.t.TBScheduleManager - 暂停调度 :DataSyncATask$192.168.56.1$4354D7079D6144AAB33F650B6FF7E532$0000000003:FetchDataCount=147,FetchDataNum=9600,DealDataSucess=9600,DealDataFail=0,DealSpendTime=687968,otherCompareCount=0
10 17:01:00.291 [DataSyncATask-0-exe1] DEBUG c.t.p.s.t.TBScheduleManager - 暂停调度 :DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000:FetchDataCount=130,FetchDataNum=180,DealDataSucess=180,DealDataFail=0,DealSpendTime=23783,otherCompareCount=0
11 17:01:00.295 [DataSyncATask-3-exe0] DEBUG c.t.p.s.t.TBScheduleManager - 暂停调度 :DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002:FetchDataCount=161,FetchDataNum=18000,DealDataSucess=18000,DealDataFail=0,DealSpendTime=1207563,otherCompareCount=0
12 17:01:00.297 [DataSyncATask-2-exe0] DEBUG c.t.p.s.t.TBScheduleManager - 暂停调度 :DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001:FetchDataCount=131,FetchDataNum=700,DealDataSucess=700,DealDataFail=0,DealSpendTime=80063,otherCompareCount=0
13 17:01:02.674 [DataSyncATask-2-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManagerStatic - DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001:不是负责任务分配的Leader,直接返回
14 17:01:02.865 [DataSyncATask-3-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManagerStatic - DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002:不是负责任务分配的Leader,直接返回
15 17:01:02.866 [DataSyncATask-4-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManagerStatic - DataSyncATask$192.168.56.1$4354D7079D6144AAB33F650B6FF7E532$0000000003:不是负责任务分配的Leader,直接返回
16 17:01:04.433 [DataSyncATask-0-HeartBeat] DEBUG c.t.p.s.zk.ScheduleDataManager4ZK - DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000:开始重新分配任务......
17 17:01:04.437 [DataSyncATask-0-HeartBeat] DEBUG c.t.p.s.zk.ScheduleDataManager4ZK -
18 TASK_TYPE=DataSyncATask:TASK_ITEM=1:CUR_SERVER=DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000:REQ_SERVER=null:DEAL_PARAMETER=
19 TASK_TYPE=DataSyncATask:TASK_ITEM=2:CUR_SERVER=DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000:REQ_SERVER=null:DEAL_PARAMETER=
20 TASK_TYPE=DataSyncATask:TASK_ITEM=3:CUR_SERVER=DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001:REQ_SERVER=null:DEAL_PARAMETER=
21 TASK_TYPE=DataSyncATask:TASK_ITEM=4:CUR_SERVER=DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001:REQ_SERVER=null:DEAL_PARAMETER=
22 TASK_TYPE=DataSyncATask:TASK_ITEM=5:CUR_SERVER=DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002:REQ_SERVER=null:DEAL_PARAMETER=
23 TASK_TYPE=DataSyncATask:TASK_ITEM=6:CUR_SERVER=DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002:REQ_SERVER=null:DEAL_PARAMETER=
24 TASK_TYPE=DataSyncATask:TASK_ITEM=7:CUR_SERVER=DataSyncATask$192.168.56.1$4354D7079D6144AAB33F650B6FF7E532$0000000003:REQ_SERVER=null:DEAL_PARAMETER=
25 TASK_TYPE=DataSyncATask:TASK_ITEM=8:CUR_SERVER=DataSyncATask$192.168.56.1$4354D7079D6144AAB33F650B6FF7E532$0000000003:REQ_SERVER=null:DEAL_PARAMETER=
26 17:01:07.658 [DataSyncATask-2-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManagerStatic - DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001:不是负责任务分配的Leader,直接返回
27 17:01:07.882 [DataSyncATask-3-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManagerStatic - DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002:不是负责任务分配的Leader,直接返回
28 17:01:07.882 [DataSyncATask-4-HeartBeat] DEBUG c.t.p.s.t.TBScheduleManagerStatic - DataSyncATask$192.168.56.1$4354D7079D6144AAB33F650B6FF7E532$0000000003:不是负责任务分配的Leader,直接返回
29 17:01:09.441 [DataSyncATask-0-HeartBeat] DEBUG c.t.p.s.zk.ScheduleDataManager4ZK - DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000:开始重新分配任务......
30 17:01:09.446 [DataSyncATask-0-HeartBeat] DEBUG c.t.p.s.zk.ScheduleDataManager4ZK -
31 TASK_TYPE=DataSyncATask:TASK_ITEM=1:CUR_SERVER=DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000:REQ_SERVER=null:DEAL_PARAMETER=
32 TASK_TYPE=DataSyncATask:TASK_ITEM=2:CUR_SERVER=DataSyncATask$192.168.56.1$D3CED88695434526B8C9220FFCD9B584$0000000000:REQ_SERVER=null:DEAL_PARAMETER=
33 TASK_TYPE=DataSyncATask:TASK_ITEM=3:CUR_SERVER=DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001:REQ_SERVER=null:DEAL_PARAMETER=
34 TASK_TYPE=DataSyncATask:TASK_ITEM=4:CUR_SERVER=DataSyncATask$192.168.56.1$FCC86240694749FD9CE84DB791448D93$0000000001:REQ_SERVER=null:DEAL_PARAMETER=
35 TASK_TYPE=DataSyncATask:TASK_ITEM=5:CUR_SERVER=DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002:REQ_SERVER=null:DEAL_PARAMETER=
36 TASK_TYPE=DataSyncATask:TASK_ITEM=6:CUR_SERVER=DataSyncATask$192.168.56.1$D4D821EBFCFA47C1865A7E42278F45A7$0000000002:REQ_SERVER=null:DEAL_PARAMETER=
37 TASK_TYPE=DataSyncATask:TASK_ITEM=7:CUR_SERVER=DataSyncATask$192.168.56.1$4354D7079D6144AAB33F650B6FF7E532$0000000003:REQ_SERVER=null:DEAL_PARAMETER=
38 TASK_TYPE=DataSyncATask:TASK_ITEM=8:CUR_SERVER=DataSyncATask$192.168.56.1$4354D7079D6144AAB33F650B6FF7E532$0000000003:REQ_SERVER=null:DEAL_PARAMETER=
5、至此同步任务配置完成,并且实现了模拟场景的要求。
八、以上也只是对TbSchedule的初步认识,更多高级应用仍然在探索中,欢迎交流。
九、向开源工作者和组织致敬,@xuannan @kongxuan,感谢对开源事业作出的任何贡献。
本文链接: 关于TbSchedule任务调度管理框架的整合部署,转载请注明。