Java application multi-machine deployment timed task solution

The processing scheme of timed tasks under Java multi-machine deployment.

This article is transferred from: http://www.cnblogs.com/xunianchong/p/6958548.html

Requirement : There are two servers that deploy the same set of code at the same time. The code contains the timing tasks that spring comes with, but only one machine is required to execute the timing tasks each time.

When I got this requirement, two simple solutions immediately popped into my mind:

  • Use ip to judge, the ip of the two machines are definitely different, specify the ip of a certain machine to run.
  • Only deploy the code for the scheduled task on one machine.

The last two options were rejected. First, what if there is a problem with the machine with the specified ip? For example, if the machine is down, will the scheduled task on the machine that is supposed to make the IP stop running? What if the ip changes due to the server migration in the future? 
The second, the same as above, is inconvenient to maintain two sets of codes.

Because the above two assumptions do not hold, we can only find another way. So I thought of using mysql to solve it. I have learned a little about the lock mechanism of mysql before, and I 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's exclusive lock. (For details, see my article: Shared locks and exclusive locks in MySQL )

Let's start the code demonstration. The main thing here is to give you a hint of ideas. The code is still very simple.

  • First you need to create a separate 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:多机定时任务调度';

 

Take a look at the table structure after completion, the comments are already very clear, you need to add some timed task names (task_name) during initialization, this should be consistent with your code, which will be mentioned later: 

  • The code 
    First look at the spring timing tasks used in my code:
<?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: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>使用Spring的 Scheduled的定时任务配置</description> <!--支持annotation的方式--> <task:annotation-driven /> <task:scheduler id="springScheduler" pool-size="10"/> <task:scheduled-tasks scheduler="springScheduler"> <!-- 测试使用, 项目启动后每隔一分钟执行一次 --> <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 that everyone has used this method of setting timed tasks. Because it comes with spring, it is very convenient to use. Here I have specified two timed tasks to simulate the situation of two machines. Both timed tasks are Executed every one minute after the project starts.

Then look at the code in this listCar:

//定时任务的名称, 这个和数据库中的task_name是保持一致的, 保证要执行该定时任务。
public static final String LIST_CAR_TASK = "listCarTask"; private ScheduleClusterTask scheduleClusterTask; //这个时间是根据spring-scheduler.xml中配置的定时刷新时间, 比如说我们以后要设置这个定时任务时4小时刷新一次 public static final long maxExpireTime = 4 * 3600; public void listCar() { if (scheduleClusterTask.isValidMachine(maxExpireTime, CommonConstants.ScheduleTaskName.LIST_CAR_TASK)) { //执行具体的task方法, doTask(); //将execute状态更新为0 scheduleClusterTask.end(LIST_CAR_TASK); } }

 

 

最后看下最核心的代码:ScheduleClusterTask.java

/**
 * 多机定时任务工具类
 * Created by WangMeng on 2017/4/12.
 */
@Service
public class ScheduleClusterTask { private ScheduleClusterEntityService scheduleClusterEntityService; /** * 这里因为两台机器都有同样的定时任务, 会同时执行这个方法,只有一台机器可以执行成功,返回true。 * @param maxExpireTime 最大的检查时间。 * @param taskName 任务名称。 * @return */ public boolean isValidMachine(long maxExpireTime, String taskName) { boolean isValid = false; try { //通过taskName去数据库中查找到该条记录, 如果大家使用的是mybatis这里需要改一下, 就是一个简单的查询操作 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 - 1000 < currentTimeMillis) { isValid = checkMachine(taskName, carIndexEntity, ip); } else if (time + maxExpireTime - 1000 < currentTimeMillis){ //这里要判断下, 如果上一次执行出现异常导致execute没有更新为0, 那么这里要判断上一次更新时间的间隔。 isValid = checkMachine(taskName, carIndexEntity, ip); } } catch (UnknownHostException e) { e.printStackTrace(); } return isValid; } /** * end方法主要是将excute(是否正在执行的标志位,0:没有执行, 1:正在执行)更新为0 * @param taskName * @return */ public boolean end (String taskName) { ScheduleClusterEntity carIndexEntity = scheduleClusterEntityService.findOne(ScheduleClusterEntity.Fields.taskName.eq(taskName)); //将execute状态更新为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; } }

 

这里还有start方法, 看看怎样的操作:

@Repository
public class DefaultScheduleClusterEntityDao extends AbstractDao<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); } }

 

核心的代码到了这里就没有了, 代码确实是非常非常的简单, 有兴趣的话大家可以在本地测试一下就可以。 
当然还有更多很好地解决方案, 我这里秉承的是最简单的处理方式, 如果大家对我这个方案有疑问或者做的不好的地方都希望大家能够提出来, 谢谢了, 最后贴上两个其他的解决方案: 
Java通过redis管理你的集群定时任务:http://www.jianshu.com/p/48c5b11b80cd 
Quartz在Spring中集群: http://sundoctor.iteye.com/blog/486055


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324745534&siteId=291194637