Today, a Java multi-machine deployment of scheduled tasks under the processing scheme.
Requirements : There are two servers deployed with the same set of code, the code is written with spring's own scheduled tasks, but each time the execution of a scheduled task requires only one machine to execute.
When I got this demand, I was in my head. Two simple solutions:
- Using IP to judge, two machine IP must be different, specify the IP of a machine to run.
- Code that deploys timed tasks on only one machine.
The last two projects were rejected by themselves. First, what if there is a problem with the specified IP machine? For example, if you have an outage, will the scheduled tasks on the IP-setting machine not work? What if this server migration causes IP changes in the future?
The second, ibid, there is to maintain two sets of code is very inconvenient.
Because the above two assumptions are not tenable, can only find another method. So I thought of using MySQL to solve, before understanding a bit of MySQL lock mechanism, know that if there are two simultaneous tasks to write the same record in the database, only one will succeed, this is the use of MySQL exclusive lock. (For more information, see my article: Shared and exclusive locks in MySQL)
The following is the beginning of the code demonstration, here is the main idea to give you a clue, the code is very simple.
- You first need to create a single table
CREATE TABLE `t_schedule_cluster` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘@cname:主键‘, `execute` int(1) NOT NULL COMMENT ‘@cname:执行状态‘, `version` int(11) NOT NULL COMMENT ‘@cname:版本号\r\n ‘, `task_name` varchar(128) NOT NULL COMMENT ‘@cname:任务名称\r\n ‘, `execute_ip` varchar(32) DEFAULT NULL COMMENT ‘@cname:执行ip\r\n ‘, `update_time` datetime DEFAULT NULL COMMENT ‘@cname:修改时间\r\n ‘, PRIMARY KEY (`id`), KEY `Index_series_id` (`execute`)) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT=‘@cname:多机定时任务调度‘;
Look at the completed table structure, the comments are already clear, the initialization needs to add some time task Name (Task_name), this will be consistent with your code, the following will be mentioned:
- Code
First look at the spring timed tasks I used in my code:
<?xml version= "1.0" encoding= "UTF-8"?><Beansxmlns="Http://www.springframework.org/schema/beans"Xmlns:xsi="Http://www.w3.org/2001/XMLSchema-instance"xmlns:task="Http://www.springframework.org/schema/task"xsi:schemalocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd Http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd "default-lazy-init="True" ><Description> using spring's scheduled scheduled task configuration</Description><!--ways to support annotation<Task:annotation-driven/><Task:schedulerId="Springscheduler"Pool-size="Ten"/><Task:scheduled-tasksScheduler= "Springscheduler" > <!--test use, execute once every minute after project start-up <task:scheduled ref= "listcaraction" method= "ListCar" cron= "0 0/1 0 * *?" /> <task:scheduled ref= "listcaraction" method= "ListCar" cron= "0 0/1 0 * *?" /> </task:scheduled-tasks></ BEANS>
I believe everyone has used this set of timing tasks, because it is spring comes with, so it is very convenient to use, here I designated two timed tasks to simulate the situation of two machines, two scheduled tasks are the project started every minute after the execution.
Then look at the code in this listcar:
The name of the timed task, which is consistent with the Task_name in the database and is guaranteed to perform the scheduled task.Publicstatic final String list_car_task = "Listcartask"; private scheduleclustertask scheduleclustertask; //this time is based on the scheduled refresh time configured in Spring-scheduler.xml, for example, we will set this scheduled task in the future 4 hours to refresh public static final long maxexpiretime = 4 * 3600;public void listcar () { if (Scheduleclustertask.isvalidmachine, CommonConstants.ScheduleTaskName.LIST_CAR_TASK) {//perform a specific TASK method, Dotask (); //update the Execute status to 0 scheduleclustertask.end (list_car_task); } }
Finally, take a look at the core code: Scheduleclustertask.java
/** * Multi-Machine Timer Task Tool class * Created by Wangmeng on 2017/4/12. */@ServicePublicClassScheduleclustertask {Private Scheduleclusterentityservice Scheduleclusterentityservice;/** * Here because both machines have the same scheduled task, this method is executed at the same time, and only one machine can perform successfully, returning true. *@param maxexpiretime The maximum check time. *@param the TaskName task name. *@return */PublicBooleanIsvalidmachine (Long maxexpiretime, String taskname) {Boolean isValid =Falsetry {Through the TaskName to find the record in the database, if you are using mybatis here need to change, is a simple query operation scheduleclusterentity carindexentity = Scheduleclusterentityservice.findone (ScheduleClusterEntity.Fields.taskName.eq (taskname));int execute = carindexentity.getexecute (); String IP = inetaddress.getlocalhost (). gethostaddress ();Long Currenttimemillis = System.currenttimemillis ();Long time = Carindexentity.getupdatetime (). GetTime ();if (execute = =0 && time + maxexpiretime-< Currenttimemillis) {isValid = Checkmachine (taskname, carindexentity, IP);}Elseif (Time + maxexpiretime-< Currenttimemillis) {To judge here, if the last occurrence of an exception resulted in execute not being updated to 0, then the interval of the last update time is determined. IsValid = Checkmachine (taskname, carindexentity, IP); } }catch (Unknownhostexception e) {e.printstacktrace ();}return isValid; }The/** * End method is mainly the Excute (whether the flag bit is being executed, 0: No execution, 1: executing) update to 0 *@param taskname *@return */PublicBooleanEnd (String taskname) {scheduleclusterentity carindexentity = Scheduleclusterentityservice.findone ( ScheduleClusterEntity.Fields.taskName.eq (TaskName));//Update the Execute status to 0 return scheduleclusterentityservice.end (carindexentity);} Private Boolean checkmachine (string taskname, Scheduleclusterentity carindexrefresh, string IP) { Return Scheduleclusterentityservice.start (TaskName, Carindexrefresh.getversion (), IP); } @Autowired public void Setscheduleclusterentityservice (scheduleclusterentityservice Scheduleclusterentityservice) { this.scheduleclusterentityservice = Scheduleclusterentityservice;}}
Here's the Start method to see how it's done:
@RepositoryPublicClassDefaultscheduleclusterentitydaoExtendsabstractdao<scheduleclusterentity> implements Scheduleclusterentitydao { @Override public boolean Start (string taskname, int version, string IP) {String sql = "Update t_schedule_cluster Set execute = 1," + "Version =", Execute_ip =?, Update_time =? "+ " where Task_name =? and version =? "; SQL s = new SQL (SQL); S.addparam (version + 1); S.addparam (IP); S.addparam (Sqltimeutils.nowtimestamp ()); S.addparam (TaskName); S.addparam (version); return 1 = = Executeupdate (s);}}
The core of the code here is not, the code is really very simple, if you are interested in the local test can be a bit.
Of course there are more good solutions, I here is the simplest way to deal with, if you have doubts about my project or do bad place all hope that everyone can put forward, thank you, finally paste two other solutions:
Java manages your cluster's scheduled tasks via Redis: HTTP://WWW.JIANSHU.COM/P/48C5B11B80CD
Quartz cluster in spring: http://sundoctor.iteye.com/blog/486055
Timed task Processing under Java application cluster (MySQL)