Tbschedule源码通读

目录

架构设计

部署架构

​ZK存储结构

调度关系

调度原理

调度任务启动流程 

核心类

初始化流程 

核心代码流程

开始启动TBScheduleManagerFactory

执行TBScheduleManagerFactory的init()

initialThread线程逻辑 

注册任务调度器registerManagerFactory()。

ManagerFactoryTimerTask重新分配调度器reRegisterManagerFactory()

根据策略重新分配调度任务的机器assignScheduleServer()

重新启动调度机reRunScheduleServer() 

管理线程组的启动,构造任务调度器TBScheduleManagerStatic

HeartBeatTimerTask心跳调度器逻辑refreshScheduleServerInfo()

this.assignScheduleTask()

初始化运行期间信息TBScheduleManagerStatic.initial()

启动任务处理器 TBScheduleManagerStatic.computerStart()

PauseOrResumeScheduleTask任务

调度处理器TBScheduleProcessorSleep

执行线程this.startThread(i)

思考与优化

设计优化

线程优化

锁优化

Tips


架构设计

部署架构

ZK存储结构

factory(永久节点)存储执行机注册的主机信息,每个执行机启动后,都会在factory下创建一个临时顺序子节点,该节点名是由TBSchedule源码生成的主机唯一标示。每个factory对应一个执行机启动的TBScheduleManagerFactory实例,每个JVM可以有多个factory实例,factory实例也可以存在于不同的JVM中。(一般一个JVM对应一个factory,即任务执行终端)

strategy(永久节点)是在调度机配置的任务策略,每个factory启动时候回去检查自己能处理哪几个strategy,如果能处理则在/rootPath/strategy/strategy1/路径下注册自己,注册的这个信息在tbschedule源码里叫做FactoryRunningInfo

baseTaskType(永久节点)存储调度机创建的任务信息

调度关系

调度原理

调度任务启动流程 

核心类

TBScheduleManagerFactoryfactory 实例对象,管理这个factory内部所有的事情。
ZKManager 负责与zk之间的连接,数据交换。
IScheduleDataManager 负责/rootPath/baseTaskType及其子节点所有数据模型维护。
ScheduleDataManger4ZK 负责/rootPath/factory、/rootPath/strategy及其字节点数据模型维护。
IStrategyTask 每个实例代表一个线程组,每个strategy可对应多个IStrategyTask实例,来真正处理配置的任务。

一个Factory处理多个strategy,每个strategy下有多个IStrategyTask对象。
TBScheduleManager实现IStrategyTask接口,一个TBScheduleManager实例与ScheduleServer、IScheduleProcessor、IScheduleTaskDeal是一对一关系。
ScheduleServer是针对某个task的调度器
IScheduleProcessor是处理任务的多线程任务处理器,控制我们任务数据的循环执行(有TBScheduleProcessorSleepTBScheduleProcessorNotSleep两种实现)
IScheduleTaskDeal是我们需要实现的任务对象(架构组提供的AbstractTaskDealSingleAbstractTaskDealMulti为其abstract实现)

初始化流程 

核心代码流程

开始启动TBScheduleManagerFactory

1、加载Schedule.xml。

2、可以看到在源码的Schedule.xml配置文件中,配置了TBScheduleManagerFactory的Bean,指定init方法作为bean初始化时的执行方法。

<beans default-autowire="byName">
    <bean id="demoTaskBean" class="com.taobao.pamirs.schedule.test.DemoTaskBean"/>
    <bean id="scheduleManagerFactory" 
class="com.taobao.pamirs.schedule.strategy.TBScheduleManagerFactory"
          init-method="init">
        <property name="timerInterval" value="10000"></property>
        <property name="zkConfig">
            <map>
                <entry key="zkConnectString" value="localhost:2181"/>
                <entry key="rootPath" value="/taobao-pamirs-schedule/demo"/>
                <entry key="zkSessionTimeout" value="60000"/>
                <entry key="userName" value="admin"/>
                <entry key="password" value="admin"/>
                <entry key="isCheckParentPath" value="true"/>
            </map>
        </property>
    </bean>
</beans>

执行TBScheduleManagerFactory的init()

1、加载zk的配置文件,初始化zkManager,连接zk。

2、开启一个单独的线程initialThread,避免启动过长而影响到其他bean的初始化等。

3、执行initialThread线程。

	public void init(Properties p) throws Exception {
	    if(this.initialThread != null){
	    	this.initialThread.stopThread();
	    }
		this.lock.lock();
		try{
			this.scheduleDataManager = null;
			this.scheduleStrategyManager = null;
		    ConsoleManager.setScheduleManagerFactory(this);
		    if(this.zkManager != null){
				this.zkManager.close();
			}
			// 加载zk的配置文件,初始化zkManager,连接zk。
			this.zkManager = new ZKManager(p);
			this.errorMessage = "Zookeeper connecting ......" + this.zkManager.getConnectStr();
			// 开启一个单独的线程initialThread,避免启动过长而影响到其他bean的初始化等。
			initialThread = new InitialThread(this);
			initialThread.setName("TBScheduleManagerFactory-initialThread");
			// 执行initialThread线程。
			initialThread.start();
		}finally{
			this.lock.unlock();
		}
	}

initialThread线程逻辑 

1、尝试连接zk。

 2、连接zk成功后,开始初始化信息到zk

class InitialThread extends Thread{
	private static transient Logger log = LoggerFactory.getLogger(InitialThread.class);
	TBScheduleManagerFactory facotry;
	boolean isStop = false;
	public InitialThread(TBScheduleManagerFactory aFactory){
		this.facotry = aFactory;
	}
	public void stopThread(){
		this.isStop = true;
	}
	@Override
	public void run() {
		facotry.lock.lock();
		try {
			int count =0;
			// 尝试连接zk,连接成功后
			while(facotry.zkManager.checkZookeeperState() == false){
				count = count + 1;
				if(count % 50 == 0){
					facotry.errorMessage = "Zookeeper connecting ......" + facotry.zkManager.getConnectStr() + " spendTime:" + count * 20 +"(ms)";
					log.error(facotry.errorMessage);
				}
				Thread.sleep(20);
				if(this.isStop ==true){
					return;
				}
			}
			// 连接zk成功后,初始化信息到zk
			facotry.initialData();
		} catch (Throwable e) {
			 log.error(e.getMessage(),e);
		}finally{
			facotry.lock.unlock();
		}

	}
	
}
 /**
     * 在Zk状态正常后回调数据初始化
     *
     * @throws Exception
     */
    public void initialData() throws Exception {
        // 初始化zk
        this.zkManager.initial();
        // 初始化baseTaskType节点
        this.scheduleDataManager = new ScheduleDataManager4ZK(this.zkManager);
        // 初始化strategy和factory节点
        this.scheduleStrategyManager = new ScheduleStrategyDataManager4ZK(this.zkManager);
        if (this.start == true) {
            // 注册调度管理器
            this.scheduleStrategyManager.registerManagerFactory(this);
            if (timer == null) {
                timer = new Timer("TBScheduleManagerFactory-Timer");
            }
            // 创建ManagerFactoryTimerTask,每2s执行一次refresh()。
            if (timerTask == null) {
                timerTask = new ManagerFactoryTimerTask(this);
                timer.schedule(timerTask, 2000, this.timerInterval);
            }
        }
    }

注册任务调度器registerManagerFactory()。

1、 在factory目录下创建瞬时有序节点。
2、根据当前ip是否在ip管理范围内,在strategy目录下添加或删除的瞬时目录节点。

3、节点名称:(IP+$+HostName+$+UUID+$Sequence)。


    /**
     * 注册ManagerFactory
     *
     * @param managerFactory
     * @return 需要全部注销的调度,例如当IP不在列表中
     * @throws Exception
     */
    public List<String> registerManagerFactory(TBScheduleManagerFactory managerFactory) throws Exception {
        // 在factory目录下创建瞬时有序节点,节点名称(IP+$+HostName+$+UUID+$Sequence)
        // 判断managerFactory的uuid是否初始化,如果没有初始化,就生成uuid,并且在factory目录下创建瞬时有序节点
        if (managerFactory.getUuid() == null) {
            String uuid = managerFactory.getIp() + "$" + managerFactory.getHostName() + "$" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
            String zkPath = this.PATH_ManagerFactory + "/" + uuid + "$";
            zkPath = this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.EPHEMERAL_SEQUENTIAL);
            managerFactory.setUuid(zkPath.substring(zkPath.lastIndexOf("/") + 1));
        } else {
            // 如果初始化完成,判断是否在zk注册,没有则factory目录下创建瞬时有序节点
            String zkPath = this.PATH_ManagerFactory + "/" + managerFactory.getUuid();
            if (this.getZooKeeper().exists(zkPath, false) == null) {
                zkPath = this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.EPHEMERAL);
            }
        }
        // 根据当前ip是否在ip管理范围内,在strategy目录下添加或删除对应的(IP+$+HostName+$+UUID+$Sequence)瞬时目录节点。
        List<String> result = new ArrayList<String>();
        // 获取
        for (ScheduleStrategy scheduleStrategy : loadAllScheduleStrategy()) {
            boolean isFind = false;
            // 任务策略不是暂停状态并且任务策略ipList不等于空
            if (ScheduleStrategy.STS_PAUSE.equalsIgnoreCase(scheduleStrategy.getSts()) == false && scheduleStrategy.getIPList() != null) {
                // 如果任务策略的ip是本地ip 或者 当前ip在任务策略内,在strategy目录下添加对应的(IP+$+HostName+$+UUID+$Sequence)瞬时目录节点。
                for (String ip : scheduleStrategy.getIPList()) {
                    if (ip.equals("127.0.0.1") || ip.equalsIgnoreCase("localhost") || ip.equals(managerFactory.getIp()) || ip.equalsIgnoreCase(managerFactory.getHostName())) {
                        //添加可管理TaskType
                        String zkPath = this.PATH_Strategy + "/" + scheduleStrategy.getStrategyName() + "/" + managerFactory.getUuid();
                        if (this.getZooKeeper().exists(zkPath, false) == null) {
                            zkPath = this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.EPHEMERAL);
                        }
                        isFind = true;
                        break;
                    }
                }
            }
            // 如果当前ip不在ip管理范围内,在strategy目录下删除对应的(IP+$+HostName+$+UUID+$Sequence)瞬时目录节点。
            if (isFind == false) {//清除原来注册的Factory
                String zkPath = this.PATH_Strategy + "/" + scheduleStrategy.getStrategyName() + "/" + managerFactory.getUuid();
                if (this.getZooKeeper().exists(zkPath, false) != null) {
                    ZKTools.deleteTree(this.getZooKeeper(), zkPath);
                    result.add(scheduleStrategy.getStrategyName());
                }
            }
        }
        return result;
    }

ManagerFactoryTimerTask重新分配调度器reRegisterManagerFactory()

1、根据最新的配置(可执行IP列表),停止当前机器不能执行的策略。

2、根据策略重新分配调度任务的机器,

3、重新启动调度机。

    /**
     * 重新分配调度器
     */
    public void reRegisterManagerFactory() throws Exception {
        // 根据最新的配置(可执行IP列表),停止当前机器不能执行的策略;
        List<String> stopList = this.getScheduleStrategyManager().registerManagerFactory(this);
        for (String strategyName : stopList) {
            this.stopServer(strategyName);
        }
        // 根据策略重新分配调度任务的机器
        this.assignScheduleServer();
        // 重新启动调度机
        this.reRunScheduleServer();
    }

根据策略重新分配调度任务的机器assignScheduleServer()

1、加载当前机器运行的所有任务策略。
2、加载任务策略的下的所有机器。

3、当前机器只有leader才能分配调度任务的机器。

4、 根据策略重新分配调度任务机器的任务数,并在zk上更新对应的ScheduleStrategyRunntime中的AssignNum。列如当前有4台机器(A,B,C,D),共10个任务(0,1..9)。首先将10个任务均等分,每个服务器可以分配到2个任务,最后剩余两个任务将给A,B服务器获得。


    /**
     * 根据策略重新分配调度任务的机器
     *
     * @throws Exception
     */
    public void assignScheduleServer() throws Exception {
        // 加载当前机器运行的所有任务策略
        for (ScheduleStrategyRunntime run : this.scheduleStrategyManager.loadAllScheduleStrategyRunntimeByUUID(this.uuid)) {
            // 加载任务策略的下的所有机器
            List<ScheduleStrategyRunntime> factoryList = this.scheduleStrategyManager.loadAllScheduleStrategyRunntimeByTaskType(run.getStrategyName());
            // 当前机器只有leader才能分配调度任务的机器
            if (factoryList.size() == 0 || this.isLeader(this.uuid, factoryList) == false) {
                continue;
            }
            ScheduleStrategy scheduleStrategy = this.scheduleStrategyManager.loadStrategy(run.getStrategyName());
            // 根据策略重新分配调度任务机器的任务数,并在zk上更新对应的ScheduleStrategyRunntime中的AssignNum。
            // 列如当前有4台机器(A,B,C,D),共10个任务(0,1..9)。首先将10个任务均等分,每个服务器可以分配到2个任务,最后剩余两个任务将给A,B服务器获得。
            int[] nums = ScheduleUtil.assignTaskNumber(factoryList.size(), scheduleStrategy.getAssignNum(), scheduleStrategy.getNumOfSingleServer());
            for (int i = 0; i < factoryList.size(); i++) {
                ScheduleStrategyRunntime factory = factoryList.get(i);
                //更新请求的服务器数量
                this.scheduleStrategyManager.updateStrategyRunntimeReqestNum(run.getStrategyName(),
                        factory.getUuid(), nums[i]);
            }
        }
    }

    public boolean isLeader(String uuid, List<ScheduleStrategyRunntime> factoryList) {
        try {
            long no = Long.parseLong(uuid.substring(uuid.lastIndexOf("$") + 1));
            for (ScheduleStrategyRunntime server : factoryList) {
                // 判断是否是leader,取zk临时节点最小的,目录下最开始创建的节点
                if (no > Long.parseLong(server.getUuid().substring(
                        server.getUuid().lastIndexOf("$") + 1))) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return true;
        }
    }

重新启动调度机reRunScheduleServer() 

1、根据每个strategyName下获得的任务数,来创建对应任务调度管理器数。

2、多则删停调度器。

3、不足则增加调度器。

    public void reRunScheduleServer() throws Exception {
        // 同时一个strategyName对应该调度服务器多个IStrategyTask任务管理器,一个taskItem对应一个任务管理器
        // 根据每个strategyName下获得的任务数,来创建对应任务调度管理器数。
        // 多则删停调度器
        for (ScheduleStrategyRunntime run : this.scheduleStrategyManager.loadAllScheduleStrategyRunntimeByUUID(this.uuid)) {
            List<IStrategyTask> list = this.managerMap.get(run.getStrategyName());
            if (list == null) {
                list = new ArrayList<IStrategyTask>();
                this.managerMap.put(run.getStrategyName(), list);
            }
            while (list.size() > run.getRequestNum() && list.size() > 0) {
                IStrategyTask task = list.remove(list.size() - 1);
                try {
                    task.stop(run.getStrategyName());
                } catch (Throwable e) {
                    logger.error("注销任务错误:" + e.getMessage(), e);
                }
            }
            //不足则增加调度器
            ScheduleStrategy strategy = this.scheduleStrategyManager.loadStrategy(run.getStrategyName());
            while (list.size() < run.getRequestNum()) {
                IStrategyTask result = this.createStrategyTask(strategy);
                list.add(result);
            }
        }
    }

管理线程组的启动,构造任务调度器TBScheduleManagerStatic

TBScheduleManagerStatic继承于TBScheduleManager。

    TBScheduleManager(TBScheduleManagerFactory aFactory, String baseTaskType, String ownSign, IScheduleDataManager aScheduleCenter) throws Exception {
        // 将对调度时期用到的参数进行赋值
        this.factory = aFactory;
        this.currentSerialNumber = serialNumber();
        this.scheduleCenter = aScheduleCenter;
        this.taskTypeInfo = this.scheduleCenter.loadTaskTypeBaseInfo(baseTaskType);
        log.info("create TBScheduleManager for taskType:" + baseTaskType);
        // 并清理过期的调度任务 清除已经过期1天的TASK,OWN_SIGN的组合。超过一天没有活动server的视为过期
        this.scheduleCenter.clearExpireTaskTypeRunningInfo(baseTaskType, ScheduleUtil.getLocalIP() + "清除过期OWN_SIGN信息", this.taskTypeInfo.getExpireOwnSignInterval());
        Object dealBean = aFactory.getBean(this.taskTypeInfo.getDealBeanName());
        // 校验客户端的自己实现的Bean,通过配置获取到全名的类,然后根据上下文获取对应的Bean. 然后利用instanceOf检验当前Bean是否实现了特定的接口
        if (dealBean == null) {
            throw new Exception("SpringBean " + this.taskTypeInfo.getDealBeanName() + " 不存在");
        }
        if (dealBean instanceof IScheduleTaskDeal == false) {
            throw new Exception("SpringBean " + this.taskTypeInfo.getDealBeanName() + " 没有实现 IScheduleTaskDeal接口");
        }
        this.taskDealBean = (IScheduleTaskDeal) dealBean;
        if (this.taskTypeInfo.getJudgeDeadInterval() < this.taskTypeInfo.getHeartBeatRate() * 5) {
            throw new Exception("数据配置存在问题,死亡的时间间隔,至少要大于心跳线程的5倍。当前配置数据:JudgeDeadInterval = "
                    + this.taskTypeInfo.getJudgeDeadInterval()
                    + ",HeartBeatRate = " + this.taskTypeInfo.getHeartBeatRate());
        }
        //生成ScheduleServer信息。
        this.currenScheduleServer = ScheduleServer.createScheduleServer(this.scheduleCenter, baseTaskType, ownSign, this.taskTypeInfo.getThreadNumber());
        //设置ScheduleServer的ManagerFactoryUUID
        this.currenScheduleServer.setManagerFactoryUUID(this.factory.getUuid());
        // 注册当前调度机 在/server下注册ScheduleServer信息,实际上可以看成在server目录下的每一个子节点表示一个任务调度管理器
        scheduleCenter.registerScheduleServer(this.currenScheduleServer);
        this.mBeanName = "pamirs:name=" + "schedule.ServerMananger." + this.currenScheduleServer.getUuid();
        this.heartBeatTimer = new Timer(this.currenScheduleServer.getTaskType() + "-" + this.currentSerialNumber + "-HeartBeat");
        // 启动心跳Timer
        this.heartBeatTimer.schedule(new HeartBeatTimerTask(this),
                new java.util.Date(System.currentTimeMillis() + 500),
                this.taskTypeInfo.getHeartBeatRate());
        // 初始化运行期间信息
        initial();
    }

HeartBeatTimerTask心跳调度器逻辑refreshScheduleServerInfo()

主要职责是更新/server目录下对应的调度管理器心跳信息,清除过期的scheduleServer,如果是leader则进行任务项的分配。

    /**
     * 定时向数据配置中心更新当前服务器的心跳信息。
     * 如果发现本次更新的时间如果已经超过了,服务器死亡的心跳周期,则不能在向服务器更新信息。
     * 而应该当作新的服务器,进行重新注册。
     */
    public void refreshScheduleServerInfo() throws Exception {
        try {
            //在/server下更新任务调度服务器的心跳时间,调度信息
            rewriteScheduleInfo();
            //如果任务信息没有初始化成功,不做任务相关的处理,未完成init()
            if (this.isRuntimeInfoInitial == false) {
                return;
            }
            //重新分配任务,leader重新检查可用调度管理器,并修改taskItem下的current_server,req_server.
            // current_server : 持有当前任务队列的任务处理器
            // req_server : 正在申请此任务队列的任务处理器
            this.assignScheduleTask();
            //判断是否需要重新加载任务队列,避免任务处理进程不必要的检查和等待
            //思路:每一次修改了taskitem的任务分配之后,会在/taskitem下保存leader信息,及默认版本号-1
            //比较保存的上一次任务加载的版本号是否  <   当前的版本号
            boolean tmpBoolean = this.isNeedReLoadTaskItemList();
            if (tmpBoolean != this.isNeedReloadTaskItem) {
                //只要不相同,就设置需要重新装载,因为在心跳异常的时候,做了清理队列的事情,恢复后需要重新装载。
                synchronized (NeedReloadTaskItemLock) {
                    this.isNeedReloadTaskItem = true;
                }
                rewriteScheduleInfo();
            }
            if (this.isPauseSchedule == true || this.processor != null && processor.isSleeping() == true) {
                //如果服务已经暂停了,则需要重新定时更新 cur_server 和 req_server
                //如果服务没有暂停,一定不能调用的
                //调度服务策略如果已经失效,会抛出异常
                //加载任务list<taskDefine>
                this.getCurrentScheduleTaskItemListNow();
            }
        } catch (Throwable e) {
            //清除内存中所有的已经取得的数据和任务队列,避免心跳线程失败时候导致的数据重复
            this.clearMemoInfo();
            if (e instanceof Exception) {
                throw (Exception) e;
            } else {
                throw new Exception(e.getMessage(), e);
            }
        }
    }

this.assignScheduleTask()

实现了任务调度管理器的变化而相应的修改/taskItem下curr_server和req_server的调度变化。核心思想:rewriteScheduleInfo()中没有相应的调度服务器,则在/server下注册。然后获取有效的所有调度服务器,遍历所有任务项,如果发现该任务项的curr_server表示的manager不存在,则设置null。然后对所有的任务分片重新分配调度服务器,具体算法如下:

    public void assignTaskItem(String taskType, String currentUuid, int maxNumOfOneServer,
                               List<String> taskServerList) throws Exception {
        if (this.isLeader(currentUuid, taskServerList) == false) {
            if (log.isDebugEnabled()) {
                log.debug(currentUuid + ":不是负责任务分配的Leader,直接返回");
            }
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug(currentUuid + ":开始重新分配任务......");
        }
        if (taskServerList.size() <= 0) {
            //在服务器动态调整的时候,可能出现服务器列表为空的清空
            return;
        }
        String baseTaskType = ScheduleUtil.splitBaseTaskTypeFromTaskType(taskType);
        String zkPath = this.PATH_BaseTaskType + "/" + baseTaskType + "/" + taskType + "/" + this.PATH_TaskItem;
        List<String> children = this.getZooKeeper().getChildren(zkPath, false);
//         Collections.sort(children);
//         20150323 有些任务分片,业务方其实是用数字的字符串排序的。优先以数字进行排序,否则以字符串排序
        Collections.sort(children, new Comparator<String>() {
            @Override
            public int compare(String u1, String u2) {
                if (StringUtils.isNumeric(u1) && StringUtils.isNumeric(u2)) {
                    int iU1 = Integer.parseInt(u1);
                    int iU2 = Integer.parseInt(u2);
                        /*if(iU1==iU2){
                            return 0 ;
                        }else if(iU1>iU2){
                            return 1 ;
                        }else{
                            return -1;
                        }*/
                    return iU1 - iU2;
                } else {
                    return u1.compareTo(u2);
                }
            }
        });
        int unModifyCount = 0;
        int[] taskNums = ScheduleUtil.assignTaskNumber(taskServerList.size(), children.size(), maxNumOfOneServer);
        int point = 0;
        int count = 0;
        String NO_SERVER_DEAL = "没有分配到服务器";
        for (int i = 0; i < children.size(); i++) {
            String name = children.get(i);
            if (point < taskServerList.size() && i >= count + taskNums[point]) {
                count = count + taskNums[point];
                point = point + 1;
            }
            String serverName = NO_SERVER_DEAL;
            if (point < taskServerList.size()) {
                serverName = taskServerList.get(point);
            }
            byte[] curServerValue = this.getZooKeeper().getData(zkPath + "/" + name + "/cur_server", false, null);
            byte[] reqServerValue = this.getZooKeeper().getData(zkPath + "/" + name + "/req_server", false, null);
            if (curServerValue == null || new String(curServerValue).equals(NO_SERVER_DEAL)) {
                //对没有分配的任务分片,添加调度服务器
                this.getZooKeeper().setData(zkPath + "/" + name + "/cur_server", serverName.getBytes(), -1);
                this.getZooKeeper().setData(zkPath + "/" + name + "/req_server", null, -1);
            } else if (new String(curServerValue).equals(serverName) == true && reqServerValue == null) {
                //不需要做任何事情   当前执行的调度器正好和重新分配的调度器一致
                unModifyCount = unModifyCount + 1;
            } else {
                //调度服务器请求转换
                this.getZooKeeper().setData(zkPath + "/" + name + "/req_server", serverName.getBytes(), -1);
            }
        }
        if (unModifyCount < children.size()) { //设置需要所有的服务器重新装载任务
            log.info("设置需要所有的服务器重新装载任务:updateReloadTaskItemFlag......" + taskType + "  ,currentUuid " + currentUuid);
            //设置/server[v.2][reload=true]
            this.updateReloadTaskItemFlag(taskType);
        }
        if (log.isDebugEnabled()) {
            StringBuffer buffer = new StringBuffer();
            for (ScheduleTaskItem taskItem : this.loadAllTaskItem(taskType)) {
                buffer.append("\n").append(taskItem.toString());
            }
            log.debug(buffer.toString());
        }
    }

初始化运行期间信息TBScheduleManagerStatic.initial()

    public void initial() throws Exception {
        new Thread(this.currenScheduleServer.getTaskType() + "-" + this.currentSerialNumber + "-StartProcess") {
            @SuppressWarnings("static-access")
            public void run() {
                try {
                    log.info("开始获取调度任务队列...... of " + currenScheduleServer.getUuid());
                    //并发启动调度管理器,直至leader初始化任务项完成
                    while (isRuntimeInfoInitial == false) {
                        if (isStopSchedule == true) {
                            log.debug("外部命令终止调度,退出调度队列获取:" + currenScheduleServer.getUuid());
                            return;
                        }
                        //log.error("isRuntimeInfoInitial = " + isRuntimeInfoInitial);
                        try {
                            initialRunningInfo();
                            //在/taskitem下的数据判断是否为leader的数据
                            isRuntimeInfoInitial = scheduleCenter.isInitialRunningInfoSucuss(
                                    currenScheduleServer.getBaseTaskType(),
                                    currenScheduleServer.getOwnSign());
                        } catch (Throwable e) {
                            //忽略初始化的异常
                            log.error(e.getMessage(), e);
                        }
                        if (isRuntimeInfoInitial == false) {
                            Thread.currentThread().sleep(1000);
                        }
                    }
                    int count = 0;
                    lastReloadTaskItemListTime = scheduleCenter.getSystemTime();
                    //此处会给currentTaskItemList添加元素,直至加载到任务
                    while (getCurrentScheduleTaskItemListNow().size() <= 0) {
                        if (isStopSchedule == true) {
                            log.debug("外部命令终止调度,退出调度队列获取:" + currenScheduleServer.getUuid());
                            return;
                        }
                        Thread.currentThread().sleep(1000);
                        count = count + 1;
                        // log.error("尝试获取调度队列,第" + count + "次 ") ;
                    }
                    String tmpStr = "TaskItemDefine:";
                    for (int i = 0; i < currentTaskItemList.size(); i++) {
                        if (i > 0) {
                            tmpStr = tmpStr + ",";
                        }
                        tmpStr = tmpStr + currentTaskItemList.get(i);
                    }
                    log.info("获取到任务处理队列,开始调度:" + tmpStr + "  of  " + currenScheduleServer.getUuid());
                    //任务总量
                    taskItemCount = scheduleCenter.loadAllTaskItem(currenScheduleServer.getTaskType()).size();
                    //只有在已经获取到任务处理队列后才开始启动任务处理器                   
                    computerStart();
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    String str = e.getMessage();
                    if (str.length() > 300) {
                        str = str.substring(0, 300);
                    }
                    startErrorInfo = "启动处理异常:" + str;
                }
            }
        }.start();
    }

启动任务处理器 TBScheduleManagerStatic.computerStart()

最后的computerStart()方法是实现周期执行的关键,TBSchedule基于cronExpression表达式实现周期性调度,执行类型分为两种TYPE_PAUSE,TYPE_RESUME。并更新setNextRunStartTime和setNextRunEndTime。

    /**
     * 开始的时候,计算第一次执行时间
     *
     * @throws Exception
     */
    public void computerStart() throws Exception {
        //只有当存在可执行队列后再开始启动队列
        boolean isRunNow = false;
        // 若开始执行时间为空,立即执行
        if (this.taskTypeInfo.getPermitRunStartTime() == null) {
            isRunNow = true;
        } else {
            // 若以startrun开头,那么设置为立即执行,并解析到后面的字符串
            String tmpStr = this.taskTypeInfo.getPermitRunStartTime();
            if (tmpStr.toLowerCase().startsWith("startrun:")) {
                isRunNow = true;
                tmpStr = tmpStr.substring("startrun:".length());
            }
            CronExpression cexpStart = new CronExpression(tmpStr);
            Date current = new Date(this.scheduleCenter.getSystemTime());
            // 获取当前时间后第一次执行的合法时间
            Date firstStartTime = cexpStart.getNextValidTimeAfter(current);
            // 开启任务执行管理timer,resume类型调度
            this.heartBeatTimer.schedule(
                    new PauseOrResumeScheduleTask(this, this.heartBeatTimer,
                            PauseOrResumeScheduleTask.TYPE_RESUME, tmpStr),
                    firstStartTime);
            // 获取第一次执行时间的下一个合法时间,设置为下一次开始执行时间
            this.currenScheduleServer.setNextRunStartTime(ScheduleUtil.transferDataToString(firstStartTime));
            // 如果没有配置结束时间,那么当获取不到数据的时候结束
            if (this.taskTypeInfo.getPermitRunEndTime() == null
                    || this.taskTypeInfo.getPermitRunEndTime().equals("-1")) {
                this.currenScheduleServer.setNextRunEndTime("当不能获取到数据的时候pause");
            } else {
                try {
                    // 获取第一次执行时间下的第一次结束时间(firstEndTime)
                    // 获取当前时间下的第一次结束时间 (nowEndTime)
                    String tmpEndStr = this.taskTypeInfo.getPermitRunEndTime();
                    CronExpression cexpEnd = new CronExpression(tmpEndStr);
                    Date firstEndTime = cexpEnd.getNextValidTimeAfter(firstStartTime);
                    Date nowEndTime = cexpEnd.getNextValidTimeAfter(current);
                    // 若firstEndTIme 和 nowEndTime不相等并且nowEndTime在当前时间后,那么设置为立即执行;并设置下次结束时间为nowEndTime;
                    if (!nowEndTime.equals(firstEndTime) && current.before(nowEndTime)) {
                        isRunNow = true;
                        firstEndTime = nowEndTime;
                    }
                    // 开启任务执行管理timer,pause类型调度
                    this.heartBeatTimer.schedule(
                            new PauseOrResumeScheduleTask(this, this.heartBeatTimer,
                                    PauseOrResumeScheduleTask.TYPE_PAUSE, tmpEndStr),
                            firstEndTime);
                    this.currenScheduleServer.setNextRunEndTime(ScheduleUtil.transferDataToString(firstEndTime));
                } catch (Exception e) {
                    log.error("计算第一次执行时间出现异常:" + currenScheduleServer.getUuid(), e);
                    throw new Exception("计算第一次执行时间出现异常:" + currenScheduleServer.getUuid(), e);
                }
            }
        }
        // 若立即执行标志位true,则立即恢复调度
        if (isRunNow == true) {
            this.resume("开启服务立即启动");
        }
        // 重写调度信息
        this.rewriteScheduleInfo();
    }

PauseOrResumeScheduleTask任务


这个调度使用同一个timer对象,每次调度执行后在timer添加新的调度task。如果是PAUSE类型调度,则执行manager.pause(“到达终止时间,pause调度”),如果是RESUME,则执行manager.resume(“到达开始时间,resume调度”);,并计算下次调度时间,重新添加到调度队列。

    public void run() {
        try {
            Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
            this.cancel();//取消调度任务
            Date current = new Date(System.currentTimeMillis());
            CronExpression cexp = new CronExpression(this.cronTabExpress);
            Date nextTime = cexp.getNextValidTimeAfter(current);
            if (this.type == TYPE_PAUSE) {
                manager.pause("到达终止时间,pause调度");
                this.manager.getScheduleServer().setNextRunEndTime(ScheduleUtil.transferDataToString(nextTime));
            } else {
                manager.resume("到达开始时间,resume调度");
                this.manager.getScheduleServer().setNextRunStartTime(ScheduleUtil.transferDataToString(nextTime));
            }
            this.timer.schedule(new PauseOrResumeScheduleTask(this.manager, this.timer, this.type, this.cronTabExpress), nextTime);
        } catch (Throwable ex) {
            log.error(ex.getMessage(), ex);
        }
    }

resume即在可执行时间区间恢复调度,根据SchduleTaskType配置的处理器类型模式Sleep或者NotSleep来初始化处理器。默认使用TBScheduleProcessorSleep处理器。

   /**
     * 处在了可执行的时间区间,恢复运行
     *
     * @throws Exception
     */
    public void resume(String message) throws Exception {
        if (this.isPauseSchedule == true) {
            if (log.isDebugEnabled()) {
                log.debug("恢复调度:" + this.currenScheduleServer.getUuid());
            }
            this.isPauseSchedule = false;
            this.pauseMessage = message;
            if (this.taskDealBean != null) {
                if (this.taskTypeInfo.getProcessorType() != null &&
                        this.taskTypeInfo.getProcessorType().equalsIgnoreCase("NOTSLEEP") == true) {
                    this.taskTypeInfo.setProcessorType("NOTSLEEP");
                    this.processor = new TBScheduleProcessorNotSleep(this,
                            taskDealBean, this.statisticsInfo);
                } else {
                    this.processor = new TBScheduleProcessorSleep(this,
                            taskDealBean, this.statisticsInfo);
                    this.taskTypeInfo.setProcessorType("SLEEP");
                }
            }
            // //更新心跳信息
            rewriteScheduleInfo();
        }
    }

调度处理器TBScheduleProcessorSleep

实现了Runnable接口,根据线程组里配置的线程数量创建执行任务的线程。

	/**
	 * 创建一个调度处理器 
	 * @param aManager
	 * @param aTaskDealBean
	 * @param aStatisticsInfo
	 * @throws Exception
	 */
	public TBScheduleProcessorSleep(TBScheduleManager aManager,
			IScheduleTaskDeal<T> aTaskDealBean,	StatisticsInfo aStatisticsInfo) throws Exception {
		this.scheduleManager = aManager;
		this.statisticsInfo = aStatisticsInfo;
		this.taskTypeInfo = this.scheduleManager.getTaskTypeInfo();
		this.taskDealBean = aTaskDealBean;
		if (this.taskDealBean instanceof IScheduleTaskDealSingle<?>) {
			if (taskTypeInfo.getExecuteNumber() > 1) {
				taskTypeInfo.setExecuteNumber(1);
			}
			isMutilTask = false;
		} else {
			isMutilTask = true;
		}
		if (taskTypeInfo.getFetchDataNumber() < taskTypeInfo.getThreadNumber() * 10) {
			logger.warn("参数设置不合理,系统性能不佳。【每次从数据库获取的数量fetchnum】 >= 【线程数量threadnum】 *【最少循环次数10】 ");
		}
		// 线程组里配置的线程数量
		for (int i = 0; i < taskTypeInfo.getThreadNumber(); i++) {
			this.startThread(i);
		}
	}

执行线程this.startThread(i)

一个执行线程的职责主要是执行自定义的IScheduleTaskDealSingle,而IScheduleTaskDealMulti可以实现批量处理,实现区别也是大同小异。

对开始执行的线程计数+1,在没有停止调度的前提下即resume状态下,执行客户自定义ScheduleTask的execute()方法,并完成执行统计。当任务队列中的所有任务Item都执行完成,队列为空时,如果正在执行任务的线程数不是最后一个线程,则等待。反之,则加载任务,有数据唤醒所有等待线程继续执行,没数据线程sleep SleepTimeNoData时间,并继续加载任务数据。

    public void run() {
        try {
            long startTime = 0;
            while (true) {
                this.m_lockObject.addThread();
                Object executeTask;
                while (true) {
                    if (this.isStopSchedule == true) {//停止队列调度
                        this.m_lockObject.realseThread();
                        this.m_lockObject.notifyOtherThread();//通知所有的休眠线程
                        synchronized (this.threadList) {
                            this.threadList.remove(Thread.currentThread());
                            if (this.threadList.size() == 0) {
                                this.scheduleManager.unRegisterScheduleServer();
                            }
                        }
                        return;
                    }
                    //加载调度任务
                    if (this.isMutilTask == false) {
                        executeTask = this.getScheduleTaskId();
                    } else {
                        executeTask = this.getScheduleTaskIdMulti();
                    }
                    if (executeTask == null) {
                        break;
                    }
                    try {//运行相关的程序
                        startTime = scheduleManager.scheduleCenter.getSystemTime();
                        if (this.isMutilTask == false) {
                            if (((IScheduleTaskDealSingle) this.taskDealBean).execute(executeTask, scheduleManager.getScheduleServer().getOwnSign()) == true) {
                                addSuccessNum(1, scheduleManager.scheduleCenter.getSystemTime()
                                                - startTime,
                                        "com.taobao.pamirs.schedule.TBScheduleProcessorSleep.run");
                            } else {
                                addFailNum(1, scheduleManager.scheduleCenter.getSystemTime()
                                                - startTime,
                                        "com.taobao.pamirs.schedule.TBScheduleProcessorSleep.run");
                            }
                        } else {
                            if (((IScheduleTaskDealMulti) this.taskDealBean)
                                    .execute((Object[]) executeTask, scheduleManager.getScheduleServer().getOwnSign()) == true) {
                                addSuccessNum(((Object[]) executeTask).length, scheduleManager.scheduleCenter.getSystemTime()
                                                - startTime,
                                        "com.taobao.pamirs.schedule.TBScheduleProcessorSleep.run");
                            } else {
                                addFailNum(((Object[]) executeTask).length, scheduleManager.scheduleCenter.getSystemTime()
                                                - startTime,
                                        "com.taobao.pamirs.schedule.TBScheduleProcessorSleep.run");
                            }
                        }
                    } catch (Throwable ex) {
                        if (this.isMutilTask == false) {
                            addFailNum(1, scheduleManager.scheduleCenter.getSystemTime() - startTime,
                                    "TBScheduleProcessor.run");
                        } else {
                            addFailNum(((Object[]) executeTask).length, scheduleManager.scheduleCenter.getSystemTime()
                                            - startTime,
                                    "TBScheduleProcessor.run");
                        }
                        logger.warn("Task :" + executeTask + " 处理失败", ex);
                    }
                }
                //当前队列中所有的任务都已经完成了。
                if (logger.isTraceEnabled()) {
                    logger.trace(Thread.currentThread().getName() + ":当前运行线程数量:" + this.m_lockObject.count());
                }
                if (this.m_lockObject.realseThreadButNotLast() == false) {
                    int size = 0;
                    Thread.currentThread().sleep(100);
                    startTime = scheduleManager.scheduleCenter.getSystemTime();
                    // 装载数据
                    size = this.loadScheduleData();
                    if (size > 0) {
                        this.m_lockObject.notifyOtherThread();
                    } else {
                        //判断当没有数据的是否,是否需要退出调度
                        if (this.isStopSchedule == false && this.scheduleManager.isContinueWhenData() == true) {
                            if (logger.isTraceEnabled()) {
                                logger.trace("没有装载到数据,start sleep");
                            }
                            this.isSleeping = true;
                            Thread.currentThread().sleep(this.scheduleManager.getTaskTypeInfo().getSleepTimeNoData());
                            this.isSleeping = false;
                            if (logger.isTraceEnabled()) {
                                logger.trace("Sleep end");
                            }
                        } else {
                            //没有数据,退出调度,唤醒所有沉睡线程
                            this.m_lockObject.notifyOtherThread();
                        }
                    }
                    this.m_lockObject.realseThread();
                } else {// 将当前线程放置到等待队列中。直到有线程装载到了新的任务数据
                    if (logger.isTraceEnabled()) {
                        logger.trace("不是最后一个线程,sleep");
                    }
                    this.m_lockObject.waitCurrentThread();
                }
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }

思考与优化

设计优化

对zookeeper的操作都是原生客户端的直接操作,维护起来易出错外,zookeeper的高可用也没有良好支持。zookeeper挂掉要重启所有调度端。

整个TBSchedule的调度,默认两秒内会执行refresh()操作,停止所有的任务调度器然后重新创建新的任务调度器。这样的好处可以使得某一个调度节点宕机,或者网络原因导致心跳失败,再或者在控制台修改了调度策略配置信息。可以动态的生效。但是如果能够基于ZK的watch机制,对系统的消耗会更小。由于在factory目录下创建的都是瞬时节点,如果某一个server宕机。zk会watch到相应的事件。同样,在ScheduleTaskType下的数据发生改变,zk同样可以watch到相应的事件。如果发现出现了上述几种情况,那么TBSchedule可以执行refresh()操作了。

线程优化

通过上面TBSchedule的源码分析,我们知道一个任务调度处理器,会创建一个timer根据cron表达式执行resume和pause操作。每一次resume都会创建TBScheduleProcessorSleep,然后初始化多个线程。

当该timer进行N次调度resume的时候,也就是系统会创建N*threadNum个线程,执行pause操作,则这些线程将会销毁。我的建议是每一个任务调度处理器,都指定1个线程数的cacheThreadPool线程池。可能会有人说,为何不指定一个ThreadNum数的fixedThreadPool。因为当timer执行多次resume的时候,如果上一次的resume还没有完成,线程池中没有空闲的线程来执行新的task,会造成线程依赖而下一调度的超时或者失败。指定cacheThreadPool,根据ThreadNum值向线程池submit ThreadNum个runnable对象。

锁优化

在任务执行器TBScheduleProcessorSleep中,t通过加载任务item (List taskItems),执行taskDealBean.selectTasks方法。获取到的数据存放在CopyOnWriteArrayList中。这里简单的介绍下写时拷贝容器CopyOnWriteArrayList,其对并发读不会加锁,而对并发写同步,当有一个写请求,首先获取锁 ,然后复制当前容器内数据,进行增删改,最后替换掉原有的数组引用,从而达到现场安全的目的,实际上该容器非常适合读多写少的场景。而目前的场景并没有读get的操作。获取容器元素调用remove()方法,同样获取锁。

既然读已经同步,那么在获取任务的时候,就不需要加synchronized关键字了。原有代码如下:

public synchronized Object getScheduleTaskId() { //可以去除synchronized

if (this.taskList.size() > 0) return this.taskList.remove(0); // 按正序处理 return null; }

Tips

1、本文代码块中注释绝大部分来自后期增加或者参考其他博客,非源码,有错误欢迎指正。

参考:

Tbschedule源码阅读1:Tbschedule调度之初始化---启动_Kevin-Jia的博客-CSDN博客

​​​​​​分布式调度框架TBSchedule源码解析_Mr_小白不白的博客-CSDN博客

入理解分布式调度框架TBSchedule及源码分析-蒲公英云

tbschedule源码解读 - 简书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值