package org.hy.common.thread; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.hy.common.Counter; import org.hy.common.Date; import org.hy.common.Help; import org.hy.common.net.ClientSocket; import org.hy.common.net.ClientSocketCluster; import org.hy.common.net.data.CommunicationResponse; import org.hy.common.xml.plugins.analyse.Cluster; /** * 任务执行程序 * * @author ZhengWei(HY) * @createDate 2013-12-16 * @version v1.0 * v2.0 2014-07-21:添加:融合XJava、任务池、线程池的功能 * v3.0 2015-11-03:添加:是否在初始时(即添加到Jobs时),就执行一次任务 * v4.0 2016-07-08:添加:支持轮询间隔:秒 * v5.0 2018-05-22:添加:预防因主机系统时间不精确,时间同步机制异常(如来回调整时间、时间跳跃、时间波动等), * 造成定时任务重复执行的可能。 * v6.0 2018-11-29 添加:在条件判定为True时,才允许执行任务。并预定义了占位符的标准。 * 可实现如下场景:某任务每天8~18点间周期执行。 * v7.0 2019-02-21 添加:定时任务的灾备机制。 * 1. 建立定时任务服务的集群,并区分出集群的Master/Slave主从服务。 * 只允许集群一台为Master主服务执行定时任务,其它Slave从服务作为备机并不执行定时任务。 * * 2. 所有Slave从服务将定时监控Master主服务是否存活。Master主服务不反向监控Slave从服务。 * * 3. 当Master主服务宕机后,从其它正常的Slave从服务中选出一台作为新的Master主服务。 * 接管执行权限,确保定时任务的执行。 * * 4. 同时,新的Master主服务为了性能,也不再监控Slave从服务,再删除定时任务Job。 */ public final class Jobs extends Job { /** 定时任务服务的灾备机制的心跳任务的XJavaID */ public static final String $JOB_DisasterRecoverys_Check = "JOB_DisasterRecoverys_Check"; /** 启动时间。即 startup() 方法的执行时间 */ private Date startTime; /** * 所有计划任务配置信息 */ private List<Job> jobList; /** * 正在运行的任务池中的任务运行数量 * * Key: Job.getCode(); */ private Counter<String> jobMonitor; /** 最小轮询间隔类型 */ private int minIntervalType; /** * 定时任务的灾备机制:灾备服务器。 * 灾备服务包括自己在内的所有服务器 。 * * 哪台服务的 Jobs.startTime 时间最早,即作为Master主服务,其它均为Slave从服务。 * 只有Master主服务有权执行任务,其它Slave从服务作为灾备服务(仍然计算任务的计划时间,但不执行任务)。 * * 此属性为:可选项。集合元素个数大于等于2时,灾备机制才生效(其中包括本服务自己,所以只少是2台服务时才生效)。 */ private List<ClientSocket> disasterRecoverys; /** 是否为Master主服务 */ private boolean isMaster; /** 定时任务服务的灾备机制的Job */ private Job disasterRecoveryJob; /** 标记 disasterRecoveryJob 是否从 jobList 中移除 */ private boolean disasterRecoveryJobIsValid; /** 灾备检测的最大次数。当连续数次检测到原Master服务的异常时,再由Slave从服务接管Master执行权限。 */ private int disasterCheckMax; /** * Slave从服务变成Master主服务前,Slave从服务每获得一次Master执行权限,此属性++。 * * 当 getMasterCount >= disasterCheckMax 时,Slave从服务才真正获得Master执行权限。 * 在此期间,原Master服务心跳再次成功时,getMasterCount将变成 0,准备开始重新计值。 * * 预防某一次心跳检测的异常(如网络原因,原Master服务是正常的),而造成多台Master服务并存的情况出现。 */ private int getMasterCount; /** 得到Master执行权限的时间点。当为Slave时,此属性为NULL */ private Date getMasterTime; public Jobs() { this.jobList = new ArrayList<Job>(); this.jobMonitor = new Counter<String>(); this.setDesc("Jobs Total scheduling"); this.disasterCheckMax = 3; this.getMasterCount = 0; this.getMasterTime = null; } /** * 创建灾备机制的心跳任务 * * @author ZhengWei(HY) * @createDate 2019-02-21 * @version v1.0 * * @return */ public synchronized Job createDisasterRecoveryJob() { if ( this.disasterRecoveryJob == null ) { this.disasterRecoveryJob = new Job(); this.disasterRecoveryJob.setXJavaID($JOB_DisasterRecoverys_Check); this.disasterRecoveryJob.setCode(this.disasterRecoveryJob.getXJavaID()); this.disasterRecoveryJob.setName("定时任务服务的灾备机制的心跳任务"); this.disasterRecoveryJob.setIntervalType(Job.$IntervalType_Minute); this.disasterRecoveryJob.setIntervalLen(1); this.disasterRecoveryJob.setStartTime("2000-01-01 00:00:00"); this.disasterRecoveryJob.setXid(this.getXJavaID()); this.disasterRecoveryJob.setMethodName("disasterRecoveryChecks"); } return this.disasterRecoveryJob; } /** * 灾备机制的心跳及设定Master/Slave主从服务 * * @author ZhengWei(HY) * @createDate 2019-02-21 * @version v1.0 * */ public List<JobDisasterRecoveryReport> disasterRecoveryChecks() { Map<ClientSocket ,CommunicationResponse> v_ResponseDatas = ClientSocketCluster.sendCommands(this.disasterRecoverys ,Cluster.getClusterTimeout() ,this.getXJavaID() ,"getDisasterRecoveryReport" ,true ,"定时任务服务的灾备心跳"); Date v_MasterStartTime = null; ClientSocket v_Master = null; List<ClientSocket> v_Slaves = new ArrayList<ClientSocket>(); int v_Succeed = 0; List<JobDisasterRecoveryReport> v_Reports = new ArrayList<JobDisasterRecoveryReport>(); JobDisasterRecoveryReport v_MasterReport = null; for (Map.Entry<ClientSocket ,CommunicationResponse> v_Item : v_ResponseDatas.entrySet()) { CommunicationResponse v_ResponseData = v_Item.getValue(); JobDisasterRecoveryReport v_Report = new JobDisasterRecoveryReport(); v_Report.setHostName(v_Item.getKey().getHostName()); v_Report.setPort( v_Item.getKey().getPort()); v_Report.setOK( false); if ( v_ResponseData.getResult() == 0 ) { if ( v_ResponseData.getData() != null && v_ResponseData.getData() instanceof JobDisasterRecoveryReport ) { v_Succeed++; JobDisasterRecoveryReport v_ReportTemp = (JobDisasterRecoveryReport)v_ResponseData.getData(); v_Report.setOK(true); v_Report.setStartTime( v_ReportTemp.getStartTime()); v_Report.setMasterTime(v_ReportTemp.getMasterTime()); if ( v_MasterStartTime == null ) { v_MasterStartTime = v_Report.getStartTime(); v_Master = v_Item.getKey(); v_Report.setMaster(true); v_MasterReport = v_Report; } else if ( v_MasterStartTime.differ(v_Report.getStartTime()) > 0 ) { v_Slaves.add(v_Master); v_MasterStartTime = v_Report.getStartTime(); v_Master = v_Item.getKey(); v_MasterReport.setMaster(false); v_Report.setMaster(true); v_MasterReport = v_Report; } else { v_Slaves.add(v_Item.getKey()); } } } v_Reports.add(v_Report); } if ( !Help.isNull(v_Slaves) ) { ClientSocketCluster.sendCommands(v_Slaves ,Cluster.getClusterTimeout() ,this.getXJavaID() ,"setMaster" ,new Object[]{false ,v_Succeed==this.disasterRecoverys.size()} ,true ,"定时任务服务的灾备机制的Slave"); } if ( v_Master != null ) { StringBuilder v_Buffer = new StringBuilder(); v_Buffer.append(Date.getNowTime().getFullMilli()); v_Buffer.append(" 定时任务服务的灾备机制的Master:"); v_Buffer.append(v_Master.getHostName()); v_Buffer.append(":"); v_Buffer.append(v_Master.getPort()); System.out.println(v_Buffer.toString()); v_Master.sendCommand(this.getXJavaID() ,"setMaster" ,new Object[]{true ,v_Succeed==this.disasterRecoverys.size()}); } return v_Reports; } /** * 运行 */ public synchronized void startup() { this.isMaster = false; this.disasterRecoveryJobIsValid = false; this.getMasterTime = null; this.getMasterCount = 0; if ( this.isDisasterRecovery() ) { // Master主服务不监控Slave从服务,只有Slave从服务监控主服务的存活性。 // 但,这里也必须先监控所有Slave从服务,只有监测到所有服务均正常时,才能移除Master主服务上的心跳监测。 // 这是为了防止双Master服务现像出现。如,在B服务在启动时,A服务已启动在先,但因为网络等原因A服务暂时无法获取心跳监测的反馈。 // B服务启动时,如果不添加上面断定,当A服务网络恢复后,就有可能出现双Master服务的问题。 this.addJob(this.createDisasterRecoveryJob()); this.disasterRecoveryJobIsValid = true; } Help.toSort(this.jobList ,"intervalType"); // 遍历初始一次所有Job的下一次执行时间,防止首次执行时等待2倍的间隔时长 if ( !Help.isNull(this.jobList) ) { this.minIntervalType = this.jobList.get(0).getIntervalType(); final Date v_Now = new Date(); for (Job v_Job : this.jobList) { v_Job.getNextTime(v_Now); } } TaskPool.putTask(this); this.startTime = new Date(); } /** * 停止。但不会马上停止所有的线程,会等待已运行中的线程运行完成后,才停止。 */ public synchronized void shutdown() { this.startTime = null; this.getMasterTime = null; this.finishTask(); } /** * 为了方便的XJava的配置文件使用 * * @param i_Job */ public void setAddJob(Job i_Job) { this.addJob(i_Job); } public synchronized void addJob(Job i_Job) { if ( i_Job == null ) { throw new NullPointerException("Job is null."); } if ( Help.isNull(i_Job.getCode()) ) { throw new NullPointerException("Job.getCode() is null."); } i_Job.setMyJobs(this); this.jobList.add(i_Job); // 是否在初始时(即添加到Jobs时),就执行一次任务 if ( i_Job.isInitExecute() ) { if ( i_Job.isAtOnceExecute() ) { i_Job.execute(); } else { this.executeJob(i_Job); } } } public synchronized void delJob(Job i_Job) { if ( i_Job == null ) { throw new NullPointerException("Job is null."); } if ( Help.isNull(i_Job.getCode()) ) { throw new NullPointerException("Job.getCode() is null."); } this.jobList.remove(i_Job); this.jobMonitor.remove(i_Job.getCode()); } /** * 删除所有定时任务 * * @author ZhengWei(HY) * @createDate 2018-05-22 * @version v1.0 * */ public synchronized void delJobs() { this.jobList.clear(); this.jobMonitor.clear(); } public Iterator<Job> getJobs() { return this.jobList.iterator(); } /** * 定时触发执行动作的方法 */ public void execute() { try { if ( this.minIntervalType == Job.$IntervalType_Second ) { Thread.sleep(1000); } else { // 保证00秒运行 Date v_Now = new Date(); Thread.sleep(1000 * (59 - v_Now.getSeconds()) + (1000 - v_Now.getMilliSecond())); v_Now = new Date(); while ( v_Now.getSeconds() >= 50 ) { // 时间同步机制异常(如时间停滞、时间回退、时间跳跃、时间波动等)时,重新sleep Add 2018-12-05 // 也防止sleep(1000)并不是真正的睡眼了1秒。 Thread.sleep(1000 * (59 - v_Now.getSeconds()) + (1000 - v_Now.getMilliSecond())); v_Now = new Date(); } } } catch (Exception exce) { exce.printStackTrace(); } Date v_Now = new Date(); Iterator<Job> v_Iter = this.jobList.iterator(); if ( this.minIntervalType == Job.$IntervalType_Second ) { while ( v_Iter.hasNext() ) { try { Job v_Job = v_Iter.next(); Date v_NextTime = v_Job.getNextTime(v_Now); if ( v_NextTime.equalsYMDHMS(v_Now) ) { if ( v_Job.isAllow(v_Now) ) { this.executeJob(v_Job); } } } catch (Exception exce) { System.out.println(exce.getMessage()); } } } else { v_Now = v_Now.getFirstTimeOfMinute(); while ( v_Iter.hasNext() ) { try { Job v_Job = v_Iter.next(); Date v_NextTime = v_Job.getNextTime(v_Now); if ( v_NextTime.equalsYMDHM(v_Now) ) { if ( v_Job.getLastTime() == null ) { if ( v_Job.isAllow(v_Now) ) { this.executeJob(v_Job); } } // 预防因主机系统时间不精确,时间同步机制异常(如来回调整时间、时间跳跃、时间波动等), // 造成定时任务重复执行的可能。 ZhengWei(HY) Add 2018-05-22 else if ( !v_Job.getLastTime().equalsYMDHM(v_Now) && v_Job.getLastTime().differ(v_Now) < 0 ) { if ( v_Job.isAllow(v_Now) ) { this.executeJob(v_Job); } } } } catch (Exception exce) { System.out.println(exce.getMessage()); } } } } /** * 执行任务 * * @param i_Job */ private void executeJob(Job i_Job) { i_Job.setMyJobs(this); if ( addMonitor(i_Job) ) { TaskPool.putTask(i_Job); } } /** * 判定Job是否被监控 * * @author ZhengWei(HY) * @createDate 2019-03-06 * @version v1.0 * * @param i_Job * @return */ public boolean isMonitor(Job i_Job) { return this.jobMonitor.containsKey(i_Job.getCode()); } private boolean addMonitor(Job i_DBT) { return this.monitor(i_DBT ,1); } /** * 注意:delMonitor()方法及monitor()方法不要加同步锁。否则会出现线程阻塞 */ public void delMonitor(Job i_Job) { this.monitor(i_Job ,-1); } /** * 监控。 * * 控件任务同时运行的线程数 * * @param i_Job * @param i_Type 1:添加监控 -1:删除监控 * @return */ private boolean monitor(Job i_Job ,int i_Type) { if ( Help.isNull(i_Job.getCode()) ) { return false; } if ( i_Type == 1 ) { if ( this.jobMonitor.containsKey(i_Job.getCode()) ) { Long v_Count = this.jobMonitor.get(i_Job.getCode()); if ( v_Count.longValue() < i_Job.getTaskCount() ) { this.jobMonitor.put(i_Job.getCode()); } else { return false; } } else { this.jobMonitor.put(i_Job.getCode()); } } else { if ( this.jobMonitor.containsKey(i_Job.getCode()) ) { this.jobMonitor.putMinus(i_Job.getCode()); } else { return false; } } return true; } /** * 获取:启动时间。即 startup() 方法的执行时间 */ public Date getStartTime() { return startTime; } /** * 是否启动的灾备机制 * * @author ZhengWei(HY) * @createDate 2019-02-21 * @version v1.0 * * @return */ public boolean isDisasterRecovery() { if ( Help.isNull(this.disasterRecoverys) ) { return false; } else if ( this.disasterRecoverys.size() >= 2 ) { return true; } else { return false; } } /** * 获取:定时任务的灾备机制:灾备服务器。 * 灾备服务包括自己在内的所有服务器 。 * * 哪台服务的 Jobs.startTime 时间最早,即作为Master主服务,其它均为Slave从服务。 * 只有Master主服务有权执行任务,其它Slave从服务作为灾备服务(仍然计算任务的计划时间,但不执行任务)。 * * 此属性为:可选项。集合元素个数大于等于2时,灾备机制才生效(其中包括本服务自己,所以只少是2台服务时才生效)。 */ public List<ClientSocket> getDisasterRecoverys() { return disasterRecoverys; } /** * 设置:定时任务的灾备机制:灾备服务器。 * 灾备服务包括自己在内的所有服务器 。 * * 哪台服务的 Jobs.startTime 时间最早,即作为Master主服务,其它均为Slave从服务。 * 只有Master主服务有权执行任务,其它Slave从服务作为灾备服务(仍然计算任务的计划时间,但不执行任务)。 * * 此属性为:可选项。集合元素个数大于等于2时,灾备机制才生效(其中包括本服务自己,所以只少是2台服务时才生效)。 * * @param disasterRecoverys */ public void setDisasterRecoverys(List<ClientSocket> disasterRecoverys) { this.disasterRecoverys = disasterRecoverys; } /** * 获取:是否为Master主服务 */ public boolean isMaster() { return isMaster; } /** * 设置:是否为Master主服务 * * @param i_IsMaster 本服务是否为Master主服务 * @param i_AllOK 是否所有服务均正常 */ public synchronized void setMaster(boolean i_IsMaster ,boolean i_AllOK) { if ( i_IsMaster && i_AllOK ) { // 当所有服务均正常时,判定出的Master主服务,才是真正的主服务,这时才能移除主服务对其它Slave从服务的监测 if ( this.disasterRecoveryJobIsValid ) { this.disasterRecoveryJobIsValid = false; if ( this.disasterRecoveryJob != null ) { // 这里没有直接调用 delJob(this.disasterRecoveryJob) 的原因是:delJob 方法也是同步的。 this.jobList.remove(this.disasterRecoveryJob); this.jobMonitor.remove(this.disasterRecoveryJob.getCode()); } System.out.println(Date.getNowTime().getFullMilli() + " 在所有服务的同意下,本服务接管定时任务的执行权限。"); } this.getMasterCount = 0; } else if ( i_IsMaster && !this.isMaster ) { this.getMasterCount++; if ( this.getMasterCount < this.disasterCheckMax ) { System.out.println(Date.getNowTime().getFullMilli() + " 本服务第 " + this.getMasterCount +" 次准备接管定时任务的执行权限,共准备 " + this.disasterCheckMax + " 次。"); return; } System.out.println(Date.getNowTime().getFullMilli() + " 本服务在第 " + this.getMasterCount + " 次正式接管定时任务的执行权限。"); this.getMasterCount = 0; } else if ( !i_IsMaster ) { this.getMasterCount = 0; } this.isMaster = i_IsMaster; if ( this.isMaster ) { if ( this.getMasterTime == null ) { this.getMasterTime = new Date(); } } else { this.getMasterTime = null; } } /** * 获取:灾备检测的最大次数。当连续数次检测到原Master服务的异常时,再由Slave从服务接管Master执行权限。 */ public int getDisasterCheckMax() { return disasterCheckMax; } /** * 设置:灾备检测的最大次数。当连续数次检测到原Master服务的异常时,再由Slave从服务接管Master执行权限。 * * @param disasterCheckMax */ public void setDisasterCheckMax(int disasterCheckMax) { this.disasterCheckMax = disasterCheckMax; } /** * 获取:得到Master执行权限的时间点。当为Slave时,此属性为NULL */ public Date getMasterTime() { return getMasterTime; } /** * 获取本机的灾备报告 * * @author ZhengWei(HY) * @createDate 2019-03-01 * @version v1.0 * * @return */ public JobDisasterRecoveryReport getDisasterRecoveryReport() { JobDisasterRecoveryReport v_Report = new JobDisasterRecoveryReport(); v_Report.setOK( true); v_Report.setMaster( this.isMaster); v_Report.setMasterTime(this.getMasterTime); v_Report.setStartTime( this.startTime); return v_Report; } }