内容简介:CSDN首发丨TBSchedule应用实战手册
作者:鲁江(TBSchedule技术交流微信订阅号ID:tbschedule),1989年8月17日生,2010年毕业后供职于《北京京东科技有限公司》。参与工作消息中间件,报表中心,推荐系统的开发与维护工作。后于2013年异动到家乡成都所在的《京东金融成都研发中心》工作,主要从事互联网支付领域开发与研究。先后参与京东钱包个人站,京东钱包后台管理,京东白条的开发工作。先后主导京东金融底层财务系统,京东金融营销系统,金融金融智能客服——智慧工作台项目。
责编:钱曙光(qianshg@csdn.net)
实战手册系列章节目录:
- TBSchedule应用实战(零)——前言
- TBSchedule应用实战(一)—— 五分钟开始你的调度之旅
- TBSchedule应用实战(二)—— 使用Spring配置TBSchedule
- TBSchedule应用实战(三)—— 配置详解(重点)
- TBSchedule应用实战(四)——“Hello,效率之神(集群篇)”(下篇更新)
- TBSchedule应用实战(五)——“Hello,效率之神(分布式计算篇)”(下篇更新)
- TBSchedule应用实战(六)——深入理解tbs之框架结构总览(下篇更新)
- TBSchedule应用实战(七)——深入理解tbs之运行机制手把手(下篇更新)
- TBSchedule应用实战(八)——常见问题速查手册(下篇更新)
TBSchedule应用实战(零)——前言
摘要:TBSchedule使用,TBSchedule快速入门。
TBSchedule(以下简称tbs)应该是国产市面上最早开源得一款“分布式定时任务调度”框架,其优越的性能,zookeeper集群注册中心,自动灾备等种种特性受到各类型互联网企业的关注与青睐。2014年tbs早期作者空玄、玄难、huijin在发布3.2.18-SNAPSHOT版本后该项目陷入长达近2年的停更,目前由署名zzllkk2003发布了3.3.3.2正式版。
作者混迹于官方tbs技术讨论群(89558542,tbs官方团队玄难,华序,空玄和笔者均在此群)已久,帮助解决问题无数,现应一众朋友邀请将tbs再一次做一个全面的介绍。虽然tbs源码层次分明,结构清晰,但涉及线程池与多线程管理知识点较多,为面向各个开发龄的朋友快速上手,作者故意反其道而行之由浅到深进行《TBSchedule应用实战》系列课程。请各位对tbs感兴趣的朋友耐心、仔细地读完每一节课程,希望哪怕只有一年 java 开发经验的朋友看完该系列之后,也能够对tbs有一个全面地认识以及较强的应用能力。
- tbs官方地址(源码支持svn迁出): http://code.taobao.org/p/tbschedule/wiki/index/
- 作者自己优化的版本地址: https://github.com/hungki/tbschedule-wed
TBSchedule应用实战(一)—— 五分钟开始你的调度之旅
摘要: 除去环境准备工作之外,我们用五分钟启动一个tbs调度器并执行一个job。
倒计时~开始!
1、控制台部分:将上节官方提供的 tbs console admin (点击下载)扔到tomcat里启动,如何使用tomcat请自行网上查询。启动成功后如下图所示(参数可能与图中不一样):
2、新建一个空项目,需要的maven依赖如下,当然也可以直接下载作者的示例工程。
示例工程下载: 点击下载
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.oschina.wed</groupId> <artifactId>tbschedule-wed-demo</artifactId> <version>1.0.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-version>4.0.0.RELEASE</spring-version> <logback-version>1.1.7</logback-version> <slf4j-version>1.7.5</slf4j-version> <java.version>1.7</java.version> <testng-version>6.9.6</testng-version> <junit-version>4.11</junit-version> </properties> <dependencies> <!-- spring dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> <exclusions> <!-- Exclude Commons Logging in favor of SLF4j --> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-version}</version> <exclusions> <!-- Exclude Commons Logging in favor of SLF4j --> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-version}</version> </dependency> <!-- commons --> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils-core</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.1</version> </dependency> <!--tbs--> <dependency> <groupId>com.taobao.pamirs.schedule</groupId> <artifactId>tbschedule</artifactId> <version>3.3.3.2</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> <exclusion> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </exclusion> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.3.3</version> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!-- logback & slf4j dependencies --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback-version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${logback-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j-version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>${slf4j-version}</version> </dependency> <!-- test --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng-version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-version}</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.2.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.7</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.9.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <artifactId>maven-source-plugin</artifactId> <configuration> <attach>true</attach> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <executions> <execution> <id>attach-javadocs</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
3、现在跟着作者敲代码,不需要理解为什么,你只需要知道5分钟之后你已经可以使用tbs了:
a)tbs调度器工厂类
package com.oschina.wed; import com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory; import java.util.HashMap; import java.util.Map; /** * TBSchedule调度器工厂类 */ public class TBScheduleFactory { /** * 获取一个调度器实例 * @param zkAddress 注册中心地址 * @param taskPath 调度器注册路径 * @param zkConnctionTimeout 注册中心连接超时时长 * @param aclUserName 如需加密,此为用户名,tbs默认采用zookeeper digest加密方式 * @param aclPwd 如需加密,此为密码,tbs默认采用zookeeper digest加密方式 * @return */ public static TBScheduleManagerFactory getTbsFactory(String zkAddress, String taskPath, String zkConnctionTimeout, String aclUserName, String aclPwd) { try { //tbs调度器的编程式注册 TBScheduleManagerFactory tbScheduleManagerFactory = new TBScheduleManagerFactory(); Map<String, String> zkConfig = new HashMap<String, String>(); zkConfig.put("zkConnectString", zkAddress); zkConfig.put("rootPath", taskPath); zkConfig.put("zkSessionTimeout", zkConnctionTimeout); zkConfig.put("userName", aclUserName); zkConfig.put("password", aclPwd); tbScheduleManagerFactory.setZkConfig(zkConfig); //初始化zk链接,建立注册信息 tbScheduleManagerFactory.init(); return tbScheduleManagerFactory; } catch (Exception ex) { ex.printStackTrace(); return null; } } }
b)任务执行类
package com.oschina.wed; import com.taobao.pamirs.schedule.IScheduleTaskDealSingle; import com.taobao.pamirs.schedule.TaskItemDefine; import org.apache.commons.lang.builder.ToStringBuilder; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Comparator; import java.util.List; /** * 示例一:演示tbs基本配置与单机调用 * * @see com.oschina.wed.TransactionVoucher 示例用交易凭证对象,我们用它来演示各类场景运算。 * @author wed * @since 2017-12-18 */ @Component("firstScheduleDemo") public class FirstScheduleDemo implements IScheduleTaskDealSingle<TransactionVoucher> { /** * 数据加工方法,处理来自selectTasks()返回的数据 * @param transactionVoucher 凭证对象 * @param owenSign 隔离作用域 * @return 是否处理成功 * @throws Exception */ @Override public boolean execute(TransactionVoucher transactionVoucher, String owenSign) throws Exception { //数据加工开始,这里只是简单的输出数据内容 System.out.println(ToStringBuilder.reflectionToString(transactionVoucher));; //数据处理结束,返回成功或失败 return true; } @Override public List<TransactionVoucher> selectTasks(String s, String s1, int i, List<TaskItemDefine> list, int i1) throws Exception { //模拟生成一条交易凭证 final TransactionVoucher tmp = new TransactionVoucher(); tmp.setId(1L); tmp.setDirection(0); tmp.setAdmount(100L); return new ArrayList<TransactionVoucher>(){ private static final long serialVersionUID = -1772752676884292373L; { add(tmp); }}; } /** * getComparator()让用户自定义生成两个task之间的比较方法,避免重复的task执行。 * 有需要的朋友自行实现该方法,并可在selectTask()方法返回前去除重复数据,本系列 * 教程不对该方法多做讲解 * * @return 自定义比较器 */ @Override public Comparator<TransactionVoucher> getComparator() { return null; } }
c)示例task类
package com.oschina.wed; public class TransactionVoucher { /** * 凭证唯一编码 */ private Long id; /** * 交易方向(0:收入 1:支出) */ private Integer direction; /** * 交易金额(单位:分) */ private Long admount; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Integer getDirection() { return direction; } public void setDirection(Integer direction) { this.direction = direction; } public Long getAdmount() { return admount; } public void setAdmount(Long admount) { this.admount = admount; } @Override public String toString() { return "TransactionVoucher{" + "id=" + id + ", direction=" + direction + ", admount=" + admount + '}'; } }
d)入口类
package com.oschina.wed; import com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { /** * 本想为大家演示脱离Spring的纯java使用tbs,但由于其注册中心 * 需利用反射在spring容器注册的bean中获取需要调度的Bean * 故此处声明Spring容器的作用只在于将job firstScheduleDemo * 注入容器,并把容器交由tbs调度器管理。 * * @param args */ public static void main(String... args) { ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml"); //实例化一个调度器 TBScheduleManagerFactory tbScheduleManagerFactory = TBScheduleFactory.getTbsFactory( "172.16.60.12:2181,172.16.60.16:2182,172.16.60.33:2183", "/tbschedule/wed/tasks", "60000", "", ""); //Spring容器交由tbs调度器管理 tbScheduleManagerFactory.setApplicationContext(context); while (true){ } } }
上方的zookeeper地址改成你自己的zookeeper地址,单机,集群均可。接着run一下,正常的话会看下以下日志:
此时不停刷屏得DEBUG的日志分别为:1)zk链接日志;2)调度器信息写入日志;3)内置心跳日志。现在我们也不去过问为什么,直接在Log4j中屏蔽掉DEBUG级别日志。之后重启,是不是清爽多了?编码的部分到此结束,相信一个熟练使用IDE的朋友根本用不了5分钟。
4、控制台配置
现在我们把填入tbs调度器中的各项参数也填入到控制台,点击保存后,再点击管理主页。
此时的管理主页如图:
小伙伴们可以进一步检查我们的调度程序是否启动成功,怎么看呢?点击机器管理,如下图如果发现你的IP在处理任务机列表里那么恭喜了,注册成功,可以开始调度了!
5、调度器以及任务配置
接下来跟着作者配置就好,所有的配置项讲解将在下一节教学讲到:
a)调度策略配置:
b)任务管理配置:
6、倒计时咔!4分59秒59
当我们回到程序,你多半会失望的发现各种各样的小问题,那么请倒回去再仔细看一遍教学。又如果你看到了以下日志:
恭喜你!调度已经成功开始,并且我们构造的交易凭证类明细也正常的输出了。
TBSchedule应用实战(二)—— 使用Spring配置TBSchedule
摘要:本节可以下载作者全教程示例源码:https://gitee.com/wednesday_lj/codes/pq70trnx18cdgsz26im4916
上节我们使用最少的代码启动的tbs调度器并且成功运行了一个job,那么反正tbs强依赖Spring,索性我们也就把二者整合在一起,整合步骤同官方文档一样,这里也就把配置文件贴上,并附上Spring启动版示例工程。
1、Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:property-placeholder location="classpath*:*.properties"/> <context:component-scan base-package="com.oschina.wed"/> <context:annotation-config/> <bean id="scheduleManagerFactory" class="com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory" init-method="init"> <property name="zkConfig"> <map> <entry key="zkConnectString" value="172.16.60.12:2181,172.16.60.16:2182,172.16.60.33:2183"/> <entry key="rootPath" value="/tbschedule/wed/tasks"/> <entry key="zkSessionTimeout" value="60000"/> <entry key="userName" value=""/> <entry key="password" value=""/> <entry key="isCheckParentPath" value="true"/> </map> </property> </bean> </beans>
2、修改入口类:
package com.oschina.wed; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { /** * 由于我们已经将调度器交给了Spring管理, * 那么此时我们需要做的只是启动容器就好。 * * @param args */ public static void main(String... args) { ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml"); while (true) { } } }
3、调度器工厂类已无用武之地,设为过期:
package com.oschina.wed; import com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory; import java.util.HashMap; import java.util.Map; /** * TBSchedule调度器工厂类 */ @Deprecated public class TBScheduleFactory { /** * 获取一个调度器实例 * @param zkAddress 注册中心地址 * @param taskPath 调度器注册路径 * @param zkConnctionTimeout 注册中心连接超时时长 * @param aclUserName 如需加密,此为用户名,tbs默认采用zookeeper digest加密方式 * @param aclPwd 如需加密,此为密码,tbs默认采用zookeeper digest加密方式 * @return */ public static TBScheduleManagerFactory getTbsFactory(String zkAddress, String taskPath, String zkConnctionTimeout, String aclUserName, String aclPwd) { try { //tbs调度器的编程式注册 TBScheduleManagerFactory tbScheduleManagerFactory = new TBScheduleManagerFactory(); Map<String, String> zkConfig = new HashMap<String, String>(); zkConfig.put("zkConnectString", zkAddress); zkConfig.put("rootPath", taskPath); zkConfig.put("zkSessionTimeout", zkConnctionTimeout); zkConfig.put("userName", aclUserName); zkConfig.put("password", aclPwd); tbScheduleManagerFactory.setZkConfig(zkConfig); //初始化zk链接,建立注册信息 tbScheduleManagerFactory.init(); return tbScheduleManagerFactory; } catch (Exception ex) { ex.printStackTrace(); return null; } } }
TBSchedule应用实战(三)—— 配置详解
摘要:此章节极为重要,要想用好,用活tbs请仔细熟读该章节。阅读过程中如果仍有不能理解的问题欢迎提问,作者会及时作答并遵照自己的理解使用作者的示例工作多配置,多体验各种配置下任务执行效果能够极大提升读者对tbs的理解程度。
本节以图文并茂的方式尽量让朋友们认清tbs中每一个配置的具体作用,一遍敲黑板,一遍重复:重点!重点!重点!
1、应用配置
<bean id="scheduleManagerFactory" class="com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory" init-method="init"> <property name="zkConfig"> <map> <!--注册中心地址,集群,单机均可,推荐集群--> <entry key="zkConnectString" value="172.16.60.12:2181,172.16.60.16:2182,172.16.60.33:2183"/> <!--调度器注册地址--> <entry key="rootPath" value="/tbschedule/wed/tasks"/> <!--zookeeper链接超时时间--> <entry key="zkSessionTimeout" value="60000"/> <!--zookeeper节点auth用户名--> <entry key="userName" value=""/> <!--zookeeper节点auth密码--> <entry key="password" value=""/> <!--是否检查调度器注册地址第一级目录是否存在--> <entry key="isCheckParentPath" value="true"/> </map> </property> </bean>
2、控制台zookeeper数据配置
- zookeeper地址:必须同应用中保持一致
- zookeeper超时:应用中为应用连接zk超时时间,控制台为控制台连接zk超时时间。
- zookeeper根目录:代表每一个调度器在zookeeper中的唯一路径,所有的调度器Server信息,执行器job信息均存于该目录。同时也可以切换该目录地址以切换不同的调度器。(例如:用户信息离线计算分布在一个三机集群,根目录地址为/tbschedule/userinfo/tasks;用户账户信息离线计算分布在另一个三级集群,根目录地址为/tbschedule/useraccounts/accountinfo/tasks。)
要点1:由于zookeeper新版本权限控制的问题,每次新增节点必须先在控制台配置并保存,然后再启动应用。否则会出现控制台一直连接不上的问题。
- zookeeper用户:如果需要对节点加密,可以填写此项
- zookeeper密码:如果需要对节点加密,可以填写此项
要点2:对Zookeeper根目录配置用户名密码不能保证节点的绝对安全,且不说再进入控制台的时候会保留上一次配置的信息。另利用zookeeper客户端也是可以对节点进行权限消除,删除等操作的。所以作者认为用户名密码不如不配,还省去管理的麻烦。
3、控制台调度策略配置
- 策略名称:调度器唯一标识名,同一个zookeeper根目录下不可重复。
- 任务类型:分三类,Schedule; Java; Bean,其中Schedule为默认也是重点,下面依次讲解。
- Schedule:调度器默认实现任务类型,采用内部实现的调度器管理(TBScheduleManagerFactory)、任务执行管理(TBScheduleManagerStatic)、调度策略(ScheduleStrategy),支持隔离域。
- Java:Java Class实现的自定义调度策略,需继承TBScheduleManager抽象类,自己实现调度策略的所有操作。
- Bean:Java Bean实现的自定义调度策略,与Java相比只是获取实例的方式不同。配置Java时tbs用的是反射,配置Bean时使用ApplicationContext.getBean()。
要点1:
- 隔离域概念:虽说现在大部分企业的个人开发机、开发环境、测试环境网络都是隔离的,但也有不少混用。此时为了不影响其它环境的正常使用就需要用到隔离域。官方没有过多的提及隔离域的用法是因为所谓的隔离域无非就是一个传入selectTasks()的参数而已,与代码耦合,使用不当很可能影响到正常业务。这里只讲解其用法但并不推荐大家使用。
- 隔离域作用:仅用于隔离IP地址,以避免在IP地址配置为全扫描时造成的环境混乱。
- 隔离域的配置:在调度策略–任务名称一栏于任务名称之后加$符号后跟自定义域名(不配置默认为BASE),例如:FirstScheduleDemo$TEST,此时隔离域为TEST
- 隔离域配置图例:
上图:启动第一个默认策略,运行名为:FirstScheduleDemo – BASE任务配置;启动第二个TEST策略,运行名为:FirstScheduleDemo – TEST任务配置。但他们实际都是运行的任务FirstScheduleDemo相同的配置,只是传入selectTasks()方法的第二个参数ownSign值为BASE或者TEST。这个神秘的参数懂了没?没什么卵用对不对? ╮(╯▽╰)╭
- 任务名称:
- 任务类型为Schedule,这里等同于策略名称;
- 任务类型为Java,这里为Java Class绝对路径;
- 任务类型为Bean,这里为SpringBean Name;
- 任务参数:自定义调度策略时需要的动态参数,Schedule任务类型没有实现该方法,因而在Schedule类型任务下无效,无需配置。
- 单JVM最大线程组数量:
- 概念:这里的单JVM不太好理解,如果是一路看着作者前几篇教程过来的朋友,把这里理解成单个调度器实例可以启动的最大线程组数量(一个线程组中默认5个子线程)。一个线程组负责执行对应任务中的一个任务项。那么不难理解,如果不限制该值,当所有实例加起来的线程组总数大于任务项时,任务项被均匀分配到线程组中,多出来的线程组作为灾备处于“待命”状态,过多的“待命”必然会有线程资源的浪费。而当其小于任务项时,多余的任务项会被分配到某一线程组下并行,一个线程组处理太多的任务项也会造成该实例占用资源升高,效率降低,极端情况下可能会压死实例。
- 如何限制该值:这里官方控制台有一个误导。假设单JVM最大线程组数量为1,最大线程组数量为3,启动一个调度器你会发现该调度器仍会启动3个线程组。此时所谓的“单JVM最大线程组数量”是没有效果的,如果有单线程运行的需求,请配置单JVM最大线程组数量为1,最大线程组数量为1,并将任务配置中的“线程数”设为1。此时该任务处于真正的单线程运行,避免业务处理中的线程安全问题。
- 作用1:灾备(重要)。当执行任务的调度器为集群时,某一调度器宕机达到60s,tbs会将任务转移到另一个调度器中的线程组运行。
- 作用2:多重并发执行。知道了线程组的概念,那么我们可以利用任务项同时激活多个线程组,再加上任务执行本身也可以配置线程数。所以此时我们可以做到单个调度器上:(线程组数*子线程数)个线程同时处理任务,作者将此场景称之为多重并发执行,其执行效率相当恐怖,但需要自己处理好任务处理幂等性的问题。
- 调度器、线程组、任务项的分配原理如下:
1、线程组在调度器中的分配 假设: 2个调度器实例:A B 5个线程组:0,1,2,3,4 其分配机制如下: A B 0 1 2 3 4 2、任务项在线程组中的分配 假设: 3个线程组:A B C 10个任务项:0,1,2,3,4,5,6,7,8,9 其分配机制如下: A B C 0 1 2 3 4 5 6 7 8 9
- 最大线程组数量:所有调度器总计可以运行的线程组数量
- 注意:结合上述分配图看,理论上,也是大多数情况下,tbs都会启动最大线程组数量的线程组以保证可容灾性。但在实际应用中偶尔也会发生线程组启动不全的情况,这种情况仅限于某任务所配置的任务项刚好整倍数于线程组数的时候。
- ip地址:调度器IP地址列表
4、控制台任务管理配置(首要按重要性排序,次要按常用度排序)
配置项 | 说明 | 要点 |
---|---|---|
任务名称 | 这个没什么好说的,与策略中不加隔离域的策略名称一致,String.subString(0,indexOf(‘$’)) | 无 |
任务处理SpringBean | 别填错了哈 | 无 |
线程数 | tbs处理任务的excute()方法是多线程并且是线程安全的。这里指定线程数量,根据机器配置情况自行调整值大小。 | 无 |
每次获取数据量 | 影响selectTasks()方法最末一个传入参数eachFetchDataNum,可用作分页。 | 官方原版只能执行分页大小,没有方法获得当前页码。这无法满足一些数据没有处理标志位字段的场景,而如果自己保存页码的话线程同步和数据一致性为题比较难保证。可以使用作者基于3.2.18-SNAPSHOT版本的优化版,接口增加了页码,多线程组,多线程下保证页码的正确性。下载链接:tbschedule-wed |
每次执行数量 | 仅在使用批量处理接口(IScheduleTaskDealMulti)时生效,tbs会将selectTasks()返回的List均分到每个线程。 | 无 |
执行开始时间 | 遵循Cron表达式标准:秒 分 时 日 月 周 年,其中“月”和“周”支持英文缩写 | 注意:官方版本到3.3.3.2版本对任务执行间隔期的处理仍有线程问题。极易发生在selectTasks()返回size>0到<=0这一瞬间,导致任务由run–>无数据判断本轮调度执行完毕pause–>等待下一次cron开始时间的到来的正常状态机。错误得变更为run–>sleep–>resume的死循环,此时任务不受控,控制台无法终止任务。这个由两个方面带来1、使用Timer.schedule()方法控制任务执行;2、zookeeper数据同本地线程不一致,很好复现。在任意任务selectTasks()方法返回前断点,等待超过zookeeper的timeout(这里指的是zookeeper server配置的timeout而不是tbs的timeout)后放开断点。此时发现zk链接断开,客户端尝试重连。但是客户端重连后基于灾备的考虑没有清理zk数据,此时Timer失效,任务转为“不停尝试获取第一轮数据,但是又获取不到”的状态(如表图1)。如果“没有数据时休眠时长”项用的默认0.5秒并且线程很多的话。发生这一情况将导致CPU时间片频繁切换导致CPU占用飙升。同时大量的日志输出也会导致硬盘IO飙升,在一些小硬盘的虚拟机甚至有可能打满硬盘导致服务器宕机。一个比较好的Cron表达式生成工具,虽然方便,但强烈建议自己学会Cron表达式,毕竟比正则表达式简单太多了:Cron在线生成工具。(常用Cron表达式中各符号的作用:“ ”:表示匹配该域的任意值,假如在Minutes域使用 ,即表示每分钟都会触发事件。“?”:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用 ,如果使用 表示不管星期几都会触发,实际上并不是这样。 “-”:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次。“/”:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次。“,”:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。 |
执行结束时间 | 用于确定何时强行终止任务的执行。格式同“执行开始时间” | 注意:除非你相当了解你的任务,否则慎用此项!官方版本到3.3.3.2版本对任务定时结束的处理仍然有问题,尤其是在间隔短、连续执行的job上配置结束时间可以说没有任何用处,甚至带来混乱。你会经常看下如表图2的报错:看起来像是任务已终止。但实际上你会发现任务仍然持续执行。 |
单线程组最大任务项 | 用于限制单线程组能够处理的最大任务项数量,请结合本文上述“单JVM最大线程组数量”配置项看。 | 注意:配置此项虽然可以避免某一线程组“过劳死”,但是配置之前请务必确保你的调度器集群中,实例数*单实例线程组数必须大于任务项总数。否非一单发生宕机,而又限制了此项,那么将会有部分任务项无法被执行到。 |
自定义参数 | 无特定要求格式,建议分隔符或JSON序列化。大小不得超过4K | 无 |
任务项 | 分布式计算时用,这里只是开发人员自己定义的一套数据切分策略的切分键。 | 注意:tbs只保证任务项的均匀分配与灾备转移,框架本身并不关心开发者采用什么样的分片策略。每个任务项,也可称作分片标记以“,”号分割即可,其如何表示没有特别的格式要求。例如一个三机分布式计算,按task唯一id分片,可传入“0,1,2,3,4”。那么接口中得到的taskItemNum值即为5,selectTasks()取数时,用task唯一id对5取模。得到0,3则在机器A上被取出,得到1,4则在机器B上被取出,得到2则在机器C上被取出执行。 |
心跳频率 | 调度器向zookeeper注册中心报告:“我还活着”的时间间隔,保持5s就好。zkServer链接数有限,该值过小会对zkServer造成不必要的压力。该值过大又不能及时发现宕机尝试重连。 | 无 |
假定服务死亡时间 | 结合心跳频率,倍数于心跳频率时间,并且强校验至少5倍于心跳频率。 | 注意:调度管理器在某一个调度器死亡超过该项时间,则自动尝试将job转移到其余节点继续执行。注意是继续执行而不是重启,敲黑板,划重点!! |
处理模式 | SLEEP 和 NOTSLEEP模式,NOTSLEEP模式下,下方两项配置无效。 | |
没有数据时休眠时长 | 字面意思 | 无 |
每次处理完数据后休眠时间 | 字面意思 | 无 |
系列剩余文章预计本月内更新完成,敬请期待!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 京东T9今年首发的一份Spring Boot实战,让开发像搭积木一样简单
- 黄图鉴别工具 nsfw 首发
- 2019年首发,有耐心且多思考
- Myeclipse 2020.5 版本首发!支持 Java14
- 全网首发!Laravel 远程代码执行漏洞 POC
- knowage-6.3.0中文版全网首发
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Data Structures and Algorithms
Alfred V. Aho、Jeffrey D. Ullman、John E. Hopcroft / Addison Wesley / 1983-1-11 / USD 74.20
The authors' treatment of data structures in Data Structures and Algorithms is unified by an informal notion of "abstract data types," allowing readers to compare different implementations of the same......一起来看看 《Data Structures and Algorithms》 这本书的介绍吧!