忙了兩個月終於有時間寫一寫。衆所周知自動化任務就是根據一些定時器時間定時任務。比如Quartz框架等。在集羣中一般有兩種方案實現自動化任務,第一種是單獨放在一個項目中和集羣獨立部署。第二種就是接下來要說的,還是在集羣中部署。具體如下:
- 建立任務表以及插入任務數據
create table `vt_schedule_cluster` (
`id` bigint (20),
`execute` int (1),
`version` int (11),
`task_name` varchar (384),
`execute_ip` varchar (96),
`update_time` datetime
);
insert into `vt_schedule_cluster` (`id`, `execute`, `version`, `task_name`, `execute_ip`, `update_time`) values('1','1','0','pushInvoiceTask','127.0.0.1','2019-12-23 22:55:00');
insert into `vt_schedule_cluster` (`id`, `execute`, `version`, `task_name`, `execute_ip`, `update_time`) values('9','1','1','pushFileTask','172.0.0.1','2019-12-23 22:52:41');
alter table vt_schedule_cluster add index index_taskname ('task_name')
加索引目的:因爲我們使用的阿里mysql8.0 多個集羣去數據查詢不加索引搶佔資源會導致死鎖。特別是多個任務的時候。
解釋字段:
id 主鍵
execute 代表任務是否正在執行 1 正在執行 0 未在執行
version 後期版本多了可以使用目前可以不使用
task_name 任務名稱 根據自己任務設置
execute_ip 搶到任務執行ip
update_time 更新時間
- 核心代碼實現
@Scheduled(cron = "0 */5 * * * ?")
public void Task() {
try {
String ip = InetAddress.getLocalHost().getHostAddress();
String threadName = Thread.currentThread().getName();
if (scheduleClusterService.isValidMachine(ip,TASK_NAME)) {
logger.info(String.format("獲取任務的機器ip%s,線程名稱%s", ip,threadName));
/**
寫您的業務代碼
**/
}
scheduleClusterService.updateTaskInfoExeEnd(TASK_NAME);
}else {
System.err.println(String.format("沒有獲取到任務的ip%s,線程名稱%s", ip,threadName));
}
} catch (Exception e) {
// TODO Auto-generated catch block
logger.error("自動推送任務異常"+e.getMessage(),e);
}
}
@Override
public boolean isValidMachine(String ip,String taskName) {
boolean isValid = false;
ScheduleCluster selectTaskInfo = selectTaskInfo(taskName);
if (selectTaskInfo.getExecute() == 0) {
selectTaskInfo.setExecuteIp(ip);
selectTaskInfo.setExecute(1);
selectTaskInfo.setUpdateTime(new Date());
isValid = checkMachine(selectTaskInfo);
}
return isValid;
}
/**
* 更新
* @Title: checkMachine
* @author drj
* @Description: TODO(更新任務狀態爲1代表正在執行)
* @param @param task
* @param @return 參數
* @return boolean 返回類型
* @date 2020年1月1日 下午9:29:31
*/
private boolean checkMachine(ScheduleCluster task) {
int a = updateTaskInfoExe(task);
return a == 1 ? true : false;
}
- sql 語句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vtax.mapper.ScheduleClusterMapper">
<resultMap type="com.vtax.invoice.entity.ScheduleCluster"
id="BaseResultMap">
<id column="id" property="id" jdbcType="INTEGER" />
<result column="execute" property="execute" jdbcType="INTEGER" />
<result column="version" property="version" jdbcType="INTEGER" />
<result column="task_name" property="taskName" jdbcType="VARCHAR" />
<result column="execute_ip" property="executeIp" jdbcType="VARCHAR" />
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
</resultMap>
<!-- 查詢任務的sql 在mysql8.0 中最好使用*。還有就是使用for update 排它鎖保證數據不被其他更新 -->
<select id="selectTaskInfo" parameterType="String" resultMap="BaseResultMap">
select *from vt_schedule_cluster where task_name = #{taskName,jdbcType=VARCHAR} for update
</select>
<!-- 更新任務的接口 -->
<update id="updateTaskInfoExeEnd" parameterType="String">
update vt_schedule_cluster set execute = 0 where task_name = #{taskName,jdbcType=VARCHAR}
</update>
<!-- 更新任務的接口 -->
<update id="updateTaskInfoExe" parameterType="com.vtax.invoice.entity.ScheduleCluster">
update vt_schedule_cluster
<set>
<if test="execute != null">
execute = #{execute,jdbcType=INTEGER},
</if>
<if test="executeIp != null">
execute_ip = #{executeIp,jdbcType=VARCHAR},
</if>
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=TIMESTAMP},
</if>
</set>
where task_name = #{taskName,jdbcType=VARCHAR}
</update>
</mapper>
總結:目前這個基本可以滿足集羣自動化任務但是當遇到執行玩任務,沒有更新執行狀態 會讓後面任務一直搶不到資源,因爲他一直是1。大概是需要根據更新時間和當前時間來實現。這個後期再詳細解釋。
- 解決更新狀態失敗後一直不會執行任務的解決方案
private final static long maxTime = 1000*60;//每分鐘執行一次間隔時間
/**
* 驗證任務是否有效
* @return
*/
@Override
@Transactional
public boolean isValidMachine(String ip,String taskName,long maxTime) {
boolean isValid = false;
long currentTimeMillis = System.currentTimeMillis();//當前時間
ScheduleCluster selectTaskInfo = selectTaskInfo(taskName);
long updateTime = selectTaskInfo.getUpdateTime().getTime();//上次更新時間
if (selectTaskInfo.getExecute() == 0) {
selectTaskInfo.setExecuteIp(ip);
selectTaskInfo.setExecute(1);
selectTaskInfo.setUpdateTime(new Date());
isValid = checkMachine(selectTaskInfo);
}else if(updateTime + maxTime -1000 < currentTimeMillis) {
/**
假如上次更新時間加定時任務時間間隔-1000(1000代表大約執行任務消耗的時間)具體看自己任務執行時間。還小於當前時間的話肯定是上次更新失敗導致更新時間還是上上次的所以這次應該獲取任務。
**/
isValid = checkMachine(selectTaskInfo);
}
System.out.println("select data is getExecute " + selectTaskInfo.getExecute());
return isValid;
}
正對上訴問題有疑問可以留言討論。