COMPLETED = Arrays.asList(SUCCESS.status, FAIL.status, STOP.status);
+}
diff --git a/easy-retry-common/easy-retry-common-core/src/main/java/com/aizuda/easy/retry/common/core/handler/RestExceptionHandler.java b/easy-retry-common/easy-retry-common-core/src/main/java/com/aizuda/easy/retry/common/core/handler/RestExceptionHandler.java
index 09150807..a32ea6af 100644
--- a/easy-retry-common/easy-retry-common-core/src/main/java/com/aizuda/easy/retry/common/core/handler/RestExceptionHandler.java
+++ b/easy-retry-common/easy-retry-common-core/src/main/java/com/aizuda/easy/retry/common/core/handler/RestExceptionHandler.java
@@ -29,7 +29,12 @@ import java.util.stream.Collectors;
* @author: byteblogs
* @date: 2019/09/30 17:02
*/
-@ControllerAdvice(basePackages = {"com.aizuda.easy.retry.client.core", "com.aizuda.easy.retry.server"} )
+@ControllerAdvice(basePackages = {
+ "com.aizuda.easy.retry.client.core",
+ "com.aizuda.easy.retry.client.job.core",
+ "com.aizuda.easy.retry.client.common",
+ "com.aizuda.easy.retry.server",
+} )
@Slf4j
@ResponseBody
public class RestExceptionHandler {
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskInstanceMapper.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskBatchMapper.java
similarity index 54%
rename from easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskInstanceMapper.java
rename to easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskBatchMapper.java
index dbdca5ac..a76c6cd3 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskInstanceMapper.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskBatchMapper.java
@@ -1,18 +1,20 @@
package com.aizuda.easy.retry.template.datasource.persistence.mapper;
-import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskInstance;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
/**
*
- * 任务实例 Mapper 接口
+ * 调度任务 Mapper 接口
*
*
* @author www.byteblogs.com
* @since 2023-09-24
*/
@Mapper
-public interface JobTaskInstanceMapper extends BaseMapper {
+public interface JobTaskBatchMapper extends BaseMapper {
+ int updateJobTaskBatchStatus(@Param("taskBatchId") Long taskBatchId, @Param("taskStatus") Integer taskStatus);
}
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskMapper.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskMapper.java
index d146f5bf..ddd6eb21 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskMapper.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/mapper/JobTaskMapper.java
@@ -6,7 +6,7 @@ import org.apache.ibatis.annotations.Mapper;
/**
*
- * 调度任务 Mapper 接口
+ * 任务实例 Mapper 接口
*
*
* @author www.byteblogs.com
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/GroupConfig.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/GroupConfig.java
index 5e35abec..5ed1d502 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/GroupConfig.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/GroupConfig.java
@@ -27,6 +27,8 @@ public class GroupConfig implements Serializable {
private Integer initScene;
+ private Integer bucketIndex;
+
private String description;
private LocalDateTime createDt;
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/Job.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/Job.java
index 852eb7f1..2d13f5ab 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/Job.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/Job.java
@@ -109,6 +109,16 @@ public class Job implements Serializable {
*/
private Integer retryInterval;
+ /**
+ * 任务类型
+ */
+ private Integer taskType;
+
+ /**
+ * 并行数
+ */
+ private Integer parallelNum;
+
/**
* bucket
*/
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobLogMessage.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobLogMessage.java
index 0829dda4..972500d2 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobLogMessage.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobLogMessage.java
@@ -3,8 +3,10 @@ package com.aizuda.easy.retry.template.datasource.persistence.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
+
import java.io.Serializable;
import java.time.LocalDateTime;
+
import lombok.Getter;
import lombok.Setter;
@@ -26,7 +28,7 @@ public class JobLogMessage implements Serializable {
/**
* 主键
*/
- @TableId(value = "id", type = IdType.AUTO)
+ @TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
@@ -42,18 +44,23 @@ public class JobLogMessage implements Serializable {
/**
* 任务实例id
*/
- private Long taskId;
+ private Long taskBatchId;
/**
* 调度任务id
*/
- private Long taskInstanceId;
+ private Long taskId;
/**
* 创建时间
*/
private LocalDateTime createDt;
+ /**
+ * 客户端信息
+ */
+ private String clientAddress;
+
/**
* 调度信息
*/
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTask.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTask.java
index b469ec21..ada7dc80 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTask.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTask.java
@@ -12,7 +12,7 @@ import lombok.Setter;
/**
*
- * 调度任务
+ * 任务实例
*
*
* @author www.byteblogs.com
@@ -37,24 +37,54 @@ public class JobTask implements Serializable {
private String groupName;
/**
- * 任务id
+ * 任务信息id
*/
private Long jobId;
+ /**
+ * 调度任务id
+ */
+ private Long taskBatchId;
+
+ /**
+ * 父执行器id
+ */
+ private Long parentId;
+
+ /**
+ * 执行的状态 0、失败 1、成功
+ */
+ private Integer executeStatus;
+
/**
* 重试次数
*/
private Integer retryCount;
/**
- * 任务状态 0、失败 1、成功
+ * 执行结果
*/
- private Integer taskStatus;
+ private String resultMessage;
/**
- * 客户端节点id
+ * 客户端ID
*/
- private String hostId;
+ private String clientId;
+
+ /**
+ * 执行方法参数
+ */
+ private String argsStr;
+
+ /**
+ * 参数类型 text/json
+ */
+ private String argsType;
+
+ /**
+ * 扩展字段
+ */
+ private String extAttrs;
/**
* 创建时间
@@ -66,10 +96,5 @@ public class JobTask implements Serializable {
*/
private LocalDateTime updateDt;
- /**
- * 逻辑删除 1、删除
- */
- private Integer deleted;
-
}
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTaskInstance.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTaskBatch.java
similarity index 68%
rename from easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTaskInstance.java
rename to easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTaskBatch.java
index 3af883ad..80d546c5 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTaskInstance.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/JobTaskBatch.java
@@ -3,14 +3,16 @@ package com.aizuda.easy.retry.template.datasource.persistence.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
+
import java.io.Serializable;
import java.time.LocalDateTime;
+
import lombok.Getter;
import lombok.Setter;
/**
*
- * 任务实例
+ * 调度任务
*
*
* @author www.byteblogs.com
@@ -18,15 +20,15 @@ import lombok.Setter;
*/
@Getter
@Setter
-@TableName("job_task_instance")
-public class JobTaskInstance implements Serializable {
+@TableName("job_task_batch")
+public class JobTaskBatch implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
- @TableId(value = "id", type = IdType.AUTO)
+ @TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
@@ -40,34 +42,34 @@ public class JobTaskInstance implements Serializable {
private Long jobId;
/**
- * 调度任务id
+ * 任务状态 0、失败 1、成功
*/
- private Long taskId;
-
- /**
- * 父执行器id
- */
- private Long parentId;
-
- /**
- * 执行的状态 0、失败 1、成功
- */
- private Integer executeStatus;
-
- /**
- * 执行结果
- */
- private String resultMessage;
+ private Integer taskStatus;
/**
* 创建时间
*/
private LocalDateTime createDt;
+ /**
+ * 任务执行时间
+ */
+ private LocalDateTime executionAt;
+
+ /**
+ * 操作原因
+ */
+ private Integer operationReason;
+
/**
* 修改时间
*/
private LocalDateTime updateDt;
+ /**
+ * 逻辑删除 1、删除
+ */
+ private Integer deleted;
+
}
diff --git a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/SceneConfig.java b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/SceneConfig.java
index b2bd9324..ddd94c3c 100644
--- a/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/SceneConfig.java
+++ b/easy-retry-datasource/easy-retry-datasource-template/src/main/java/com/aizuda/easy/retry/template/datasource/persistence/po/SceneConfig.java
@@ -29,8 +29,6 @@ public class SceneConfig implements Serializable {
private Long deadlineRequest;
- private Integer bucketIndex;
-
private LocalDateTime createDt;
private LocalDateTime updateDt;
diff --git a/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskBatchMapper.xml b/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskBatchMapper.xml
new file mode 100644
index 00000000..af4ed398
--- /dev/null
+++ b/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskBatchMapper.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ update job_task_batch
+ set task_status = #{taskStatus}
+
+ id = #{taskBatchId}
+
+ and EXISTS (select id from job_task where task_batch_id = #{taskBatchId} and execute_status = 4)
+
+
+ and NOT EXISTS (select id from job_task where task_batch_id = #{taskBatchId} and execute_status IN(1, 2, 4, 5))
+
+
+ and NOT EXISTS (select id from job_task where task_batch_id = #{taskBatchId} and execute_status IN(1, 2))
+
+
+
+
+
+
diff --git a/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskInstanceMapper.xml b/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskInstanceMapper.xml
deleted file mode 100644
index 711a0800..00000000
--- a/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskInstanceMapper.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskMapper.xml b/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskMapper.xml
index 487f89cb..fc8de3df 100644
--- a/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskMapper.xml
+++ b/easy-retry-datasource/easy-retry-mysql-datasource/src/main/resources/mysql/mapper/JobTaskMapper.xml
@@ -7,11 +7,12 @@
-
-
+
+
+
+
-
diff --git a/easy-retry-server/easy-retry-server-common/pom.xml b/easy-retry-server/easy-retry-server-common/pom.xml
index 2befcf2a..b0caf077 100644
--- a/easy-retry-server/easy-retry-server-common/pom.xml
+++ b/easy-retry-server/easy-retry-server-common/pom.xml
@@ -86,6 +86,10 @@
com.squareup.okhttp3
okhttp
+
+ com.github.rholder
+ guava-retrying
+
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/akka/ActorGenerator.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/akka/ActorGenerator.java
index 3fc845e7..48dc4f99 100644
--- a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/akka/ActorGenerator.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/akka/ActorGenerator.java
@@ -28,6 +28,10 @@ public class ActorGenerator {
public static final String SCAN_JOB_ACTOR = "ScanJobActor";
public static final String JOB_TASK_PREPARE_ACTOR = "JobTaskPrepareActor";
public static final String JOB_EXECUTOR_ACTOR = "JobExecutorActor";
+ public static final String JOB_EXECUTOR_RESULT_ACTOR = "JobExecutorResultActor";
+ public static final String JOB_LOG_ACTOR = "JobLogActor";
+ public static final String REAL_JOB_EXECUTOR_ACTOR = "RealJobExecutorActor";
+ public static final String REAL_STOP_TASK_INSTANCE_ACTOR = "RealStopTaskInstanceActor";
private ActorGenerator() {}
@@ -149,6 +153,42 @@ public class ActorGenerator {
return getJobActorSystem().actorOf(getSpringExtension().props(JOB_EXECUTOR_ACTOR));
}
+ /**
+ * Job任务执行结果actor
+ *
+ * @return actor 引用
+ */
+ public static ActorRef jobTaskExecutorResultActor() {
+ return getJobActorSystem().actorOf(getSpringExtension().props(JOB_EXECUTOR_RESULT_ACTOR));
+ }
+
+ /**
+ * Job任务向客户端发起请求阶段actor
+ *
+ * @return actor 引用
+ */
+ public static ActorRef jobRealTaskExecutorActor() {
+ return getJobActorSystem().actorOf(getSpringExtension().props(REAL_JOB_EXECUTOR_ACTOR));
+ }
+
+ /**
+ * Job任务向客户端发起请求阶段actor
+ *
+ * @return actor 引用
+ */
+ public static ActorRef jobRealStopTaskInstanceActor() {
+ return getJobActorSystem().actorOf(getSpringExtension().props(REAL_STOP_TASK_INSTANCE_ACTOR));
+ }
+
+ /**
+ * Job日志actor
+ *
+ * @return actor 引用
+ */
+ public static ActorRef jobLogActor() {
+ return getJobActorSystem().actorOf(getSpringExtension().props(JOB_LOG_ACTOR));
+ }
+
public static SpringExtension getSpringExtension() {
return SpringContext.getBeanByType(SpringExtension.class);
}
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClient.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClient.java
index 5ba3b88a..635da1b1 100644
--- a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClient.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClient.java
@@ -1,9 +1,9 @@
package com.aizuda.easy.retry.server.common.client;
-import com.aizuda.easy.retry.client.model.DispatchJobDTO;
+import com.aizuda.easy.retry.client.model.request.DispatchJobRequest;
import com.aizuda.easy.retry.client.model.DispatchRetryDTO;
import com.aizuda.easy.retry.client.model.DispatchRetryResultDTO;
-import com.aizuda.easy.retry.client.model.InterruptJobDTO;
+import com.aizuda.easy.retry.client.model.StopJobDTO;
import com.aizuda.easy.retry.client.model.RetryCallbackDTO;
import com.aizuda.easy.retry.common.core.model.EasyRetryHeaders;
import com.aizuda.easy.retry.common.core.model.Result;
@@ -26,10 +26,10 @@ public interface RpcClient {
@Mapping(path = "/retry/callback/v1", method = RequestMethod.POST)
Result callback(@Body RetryCallbackDTO retryCallbackDTO);
- @Mapping(path = "/job/interrupt/v1", method = RequestMethod.POST)
- Result interrupt(@Body InterruptJobDTO interruptJobDTO);
+ @Mapping(path = "/job/stop/v1", method = RequestMethod.POST)
+ Result stop(@Body StopJobDTO stopJobDTO);
@Mapping(path = "/job/dispatch/v1", method = RequestMethod.POST)
- Result dispatch(@Body DispatchJobDTO dispatchJobDTO);
+ Result dispatch(@Body DispatchJobRequest dispatchJobRequest);
}
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClientInvokeHandler.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClientInvokeHandler.java
index e02d0809..346d1a16 100644
--- a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClientInvokeHandler.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/RpcClientInvokeHandler.java
@@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.URLUtil;
import com.aizuda.easy.retry.common.core.constant.SystemConstants;
import com.aizuda.easy.retry.common.core.context.SpringContext;
+import com.aizuda.easy.retry.common.core.log.LogUtils;
import com.aizuda.easy.retry.common.core.model.Result;
import com.aizuda.easy.retry.common.core.util.HostUtils;
import com.aizuda.easy.retry.common.core.util.JsonUtil;
@@ -15,13 +16,23 @@ import com.aizuda.easy.retry.server.common.client.annotation.Param;
import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
import com.aizuda.easy.retry.server.common.handler.ClientNodeAllocateHandler;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.github.rholder.retry.Attempt;
+import com.github.rholder.retry.RetryException;
+import com.github.rholder.retry.RetryListener;
+import com.github.rholder.retry.Retryer;
+import com.github.rholder.retry.RetryerBuilder;
+import com.github.rholder.retry.StopStrategies;
+import com.github.rholder.retry.WaitStrategies;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
@@ -34,6 +45,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
/**
* 请求处理器
@@ -71,11 +84,23 @@ public class RpcClientInvokeHandler implements InvocationHandler {
Mapping annotation = method.getAnnotation(Mapping.class);
Assert.notNull(annotation, () -> new EasyRetryServerException("@Mapping cannot be null"));
+ if (annotation.failover()) {
+ return doFailoverHandler(method, args, annotation);
+ }
+
+ return requestRemote(method, args, annotation, 0);
+ }
+
+ @NotNull
+ private Result doFailoverHandler(final Method method, final Object[] args, final Mapping annotation)
+ throws Throwable {
Set serverNodeSet = CacheRegisterTable.getServerNodeSet(groupName);
+
// 最多调用size次
int size = serverNodeSet.size();
for (int count = 1; count <= size; count++) {
- log.info("Start request client. count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count, hostId, hostIp, hostPort, HostUtils.getIp());
+ log.info("Start request client. count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count, hostId,
+ hostIp, hostPort, HostUtils.getIp());
Result result = requestRemote(method, args, annotation, count);
if (Objects.nonNull(result)) {
return result;
@@ -85,7 +110,7 @@ public class RpcClientInvokeHandler implements InvocationHandler {
throw new EasyRetryServerException("No available nodes.");
}
- private Result requestRemote(Method method, Object[] args, Mapping mapping, int count) {
+ private Result requestRemote(Method method, Object[] args, Mapping mapping, int count) throws Throwable {
try {
@@ -99,23 +124,31 @@ public class RpcClientInvokeHandler implements InvocationHandler {
RestTemplate restTemplate = SpringContext.CONTEXT.getBean(RestTemplate.class);
- ResponseEntity response = restTemplate.exchange(
- // 拼接 url?a=1&b=1
- getUrl(mapping, parasResult.paramMap).toString(),
- // post or get
- HttpMethod.valueOf(mapping.method().name()),
- // body
- new HttpEntity<>(parasResult.body, parasResult.requestHeaders),
- // 返回值类型
- Result.class);
+ Retryer retryer = buildResultRetryer(mapping);
- log.info("Request client success. count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count, hostId, hostIp, hostPort, HostUtils.getIp());
+ Result result = retryer.call(() -> {
+ ResponseEntity response = restTemplate.exchange(
+ // 拼接 url?a=1&b=1
+ getUrl(mapping, parasResult.paramMap).toString(),
+ // post or get
+ HttpMethod.valueOf(mapping.method().name()),
+ // body
+ new HttpEntity<>(parasResult.body, parasResult.requestHeaders),
+ // 返回值类型
+ Result.class);
- return Objects.requireNonNull(response.getBody());
+ return Objects.requireNonNull(response.getBody());
+ });
+
+ log.info("Request client success. count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count, hostId,
+ hostIp, hostPort, HostUtils.getIp());
+
+ return result;
} catch (RestClientException ex) {
// 网络异常
- if (ex instanceof ResourceAccessException) {
- log.error("request client I/O error, count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count, hostId, hostIp, hostPort, HostUtils.getIp(), ex);
+ if (ex instanceof ResourceAccessException && mapping.failover()) {
+ log.error("request client I/O error, count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count,
+ hostId, hostIp, hostPort, HostUtils.getIp(), ex);
// 进行路由剔除处理
CacheRegisterTable.remove(groupName, hostId);
@@ -135,15 +168,43 @@ public class RpcClientInvokeHandler implements InvocationHandler {
} else {
// 其他异常继续抛出
- log.error("request client error.count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count, hostId, hostIp, hostPort, HostUtils.getIp(), ex);
+ log.error("request client error.count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count,
+ hostId, hostIp, hostPort, HostUtils.getIp(), ex);
throw ex;
}
} catch (Exception ex) {
- log.error("request client unknown exception. count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]", count, hostId, hostIp, hostPort, HostUtils.getIp(), ex);
- throw ex;
+ log.error("request client unknown exception. count:[{}] clientId:[{}] clientAddr:[{}:{}] serverIp:[{}]",
+ count, hostId, hostIp, hostPort, HostUtils.getIp(), ex);
+
+ Throwable throwable = ex;
+ if (ex.getClass().isAssignableFrom(RetryException.class)) {
+ RetryException re = (RetryException) ex;
+ throwable = re.getLastFailedAttempt().getExceptionCause();
+ }
+
+ throw throwable;
}
- return null;
+ return null;
+ }
+
+ private Retryer buildResultRetryer(Mapping mapping) throws InstantiationException, IllegalAccessException, NoSuchMethodException {
+ Class extends RetryListener> retryListenerClazz = mapping.retryListener();
+ RetryListener retryListener = retryListenerClazz.newInstance();
+ Method method = retryListenerClazz.getMethod("onRetry", Attempt.class);
+
+ Retryer retryer = RetryerBuilder.newBuilder()
+ .retryIfException(throwable -> mapping.failRetry())
+ .withStopStrategy(StopStrategies.stopAfterAttempt(mapping.retryTimes()))
+ .withWaitStrategy(WaitStrategies.fixedWait(mapping.retryInterval(), TimeUnit.SECONDS))
+ .withRetryListener(new RetryListener() {
+ @Override
+ public void onRetry(Attempt attempt) {
+ ReflectionUtils.invokeMethod(method, retryListener, attempt);
+ }
+ })
+ .build();
+ return retryer;
}
private StringBuilder getUrl(Mapping mapping, Map paramMap) {
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/SimpleRetryListener.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/SimpleRetryListener.java
new file mode 100644
index 00000000..3ab428b1
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/SimpleRetryListener.java
@@ -0,0 +1,17 @@
+package com.aizuda.easy.retry.server.common.client;
+
+import com.github.rholder.retry.Attempt;
+import com.github.rholder.retry.RetryListener;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-10-09 09:24
+ * @since : 2.4.0
+ */
+public class SimpleRetryListener implements RetryListener {
+
+ @Override
+ public void onRetry(final Attempt attempt) {
+
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/annotation/Mapping.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/annotation/Mapping.java
index 432aeb59..8fd292ec 100644
--- a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/annotation/Mapping.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/client/annotation/Mapping.java
@@ -1,6 +1,8 @@
package com.aizuda.easy.retry.server.common.client.annotation;
import com.aizuda.easy.retry.server.common.client.RequestMethod;
+import com.aizuda.easy.retry.server.common.client.SimpleRetryListener;
+import com.github.rholder.retry.RetryListener;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -24,4 +26,16 @@ public @interface Mapping {
String path() default "";
+ boolean failover() default false;
+
+ boolean failRetry() default false;
+
+ int retryTimes() default 3;
+
+ int retryInterval() default 1;
+
+ Class extends RetryListener> retryListener() default SimpleRetryListener.class;
+
+
+
}
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/PartitionTask.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/PartitionTask.java
new file mode 100644
index 00000000..982b68b2
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/PartitionTask.java
@@ -0,0 +1,14 @@
+package com.aizuda.easy.retry.server.common.dto;
+
+import lombok.Data;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2022-10-28 18:45
+ */
+@Data
+public class PartitionTask {
+
+ protected Long id;
+
+}
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/RegisterNodeInfo.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/RegisterNodeInfo.java
index 5203c71e..8f7733c2 100644
--- a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/RegisterNodeInfo.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/RegisterNodeInfo.java
@@ -2,6 +2,7 @@ package com.aizuda.easy.retry.server.common.dto;
import lombok.Data;
+import java.text.MessageFormat;
import java.time.LocalDateTime;
/**
@@ -13,6 +14,8 @@ import java.time.LocalDateTime;
@Data
public class RegisterNodeInfo implements Comparable {
+ private static final String URL = "http://{0}:{1}/{2}";
+
private String groupName;
private String hostId;
@@ -27,6 +30,10 @@ public class RegisterNodeInfo implements Comparable {
private String contextPath;
+ public String fullUrl() {
+ return MessageFormat.format(URL, hostIp, hostPort.toString(), contextPath);
+ }
+
@Override
public int compareTo(RegisterNodeInfo info) {
return hostId.compareTo(info.hostId);
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/ScanTask.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/ScanTask.java
index ee3bc786..cf5fadda 100644
--- a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/ScanTask.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/dto/ScanTask.java
@@ -2,6 +2,7 @@ package com.aizuda.easy.retry.server.common.dto;
import lombok.Data;
+import java.util.List;
import java.util.Set;
/**
diff --git a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/handler/ServerNodeBalance.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/handler/ServerNodeBalance.java
index e1a724c5..f68751ab 100644
--- a/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/handler/ServerNodeBalance.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/handler/ServerNodeBalance.java
@@ -222,7 +222,6 @@ public class ServerNodeBalance implements Lifecycle, Runnable {
try {
TimeUnit.SECONDS.sleep(systemProperties.getLoadBalanceCycleTime());
} catch (InterruptedException e) {
- LogUtils.error(log, "check balance interrupt");
Thread.currentThread().interrupt();
}
}
diff --git a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/listener/EndListener.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/listener/EndListener.java
similarity index 92%
rename from easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/listener/EndListener.java
rename to easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/listener/EndListener.java
index 74a4660e..cf43f0f2 100644
--- a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/listener/EndListener.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/listener/EndListener.java
@@ -1,4 +1,4 @@
-package com.aizuda.easy.retry.server.retry.task.support.listener;
+package com.aizuda.easy.retry.server.common.listener;
import com.aizuda.easy.retry.common.core.log.LogUtils;
import com.aizuda.easy.retry.server.common.Lifecycle;
diff --git a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/listener/StartListener.java b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/listener/StartListener.java
similarity index 95%
rename from easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/listener/StartListener.java
rename to easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/listener/StartListener.java
index 8d9922db..7d94d680 100644
--- a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/listener/StartListener.java
+++ b/easy-retry-server/easy-retry-server-common/src/main/java/com/aizuda/easy/retry/server/common/listener/StartListener.java
@@ -1,4 +1,4 @@
-package com.aizuda.easy.retry.server.retry.task.support.listener;
+package com.aizuda.easy.retry.server.common.listener;
import com.aizuda.easy.retry.common.core.constant.SystemConstants;
import com.aizuda.easy.retry.common.core.log.LogUtils;
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/EasyRetryJobTaskStarter.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/EasyRetryJobTaskStarter.java
new file mode 100644
index 00000000..0fcf1cc7
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/EasyRetryJobTaskStarter.java
@@ -0,0 +1,28 @@
+package com.aizuda.easy.retry.server.job;
+
+import com.aizuda.easy.retry.server.common.Lifecycle;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-09-29 23:29:44
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class EasyRetryJobTaskStarter implements Lifecycle {
+
+ @Override
+ public void start() {
+ // 检查是否还有未执行的任务,如果有则直接失败
+ log.info("easy-retry-job-task starting...");
+
+ log.info("easy-retry-job-task completed");
+ }
+
+ @Override
+ public void close() {
+ // 关闭还未执行的任务
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/BlockStrategy.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/BlockStrategy.java
index cce133c4..9ffe5770 100644
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/BlockStrategy.java
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/BlockStrategy.java
@@ -7,6 +7,5 @@ import com.aizuda.easy.retry.server.job.task.strategy.BlockStrategies.BlockStrat
* @date : 2023-09-25 17:53
*/
public interface BlockStrategy {
-
- boolean block(BlockStrategyContext context);
+ void block(BlockStrategyContext context);
}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/JobTaskConverter.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/JobTaskConverter.java
new file mode 100644
index 00000000..30b48b55
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/JobTaskConverter.java
@@ -0,0 +1,79 @@
+package com.aizuda.easy.retry.server.job.task;
+
+import com.aizuda.easy.retry.client.model.request.DispatchJobRequest;
+import com.aizuda.easy.retry.client.model.request.DispatchJobResultRequest;
+import com.aizuda.easy.retry.server.job.task.dto.*;
+import com.aizuda.easy.retry.server.job.task.generator.batch.JobTaskBatchGeneratorContext;
+import com.aizuda.easy.retry.server.job.task.generator.task.JobTaskGenerateContext;
+import com.aizuda.easy.retry.server.job.task.handler.callback.ClientCallbackContext;
+import com.aizuda.easy.retry.server.job.task.handler.executor.JobExecutorContext;
+import com.aizuda.easy.retry.server.job.task.handler.stop.TaskStopJobContext;
+import com.aizuda.easy.retry.server.job.task.strategy.BlockStrategies;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobLogMessage;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2021-11-26 15:22
+ */
+@Mapper
+public interface JobTaskConverter {
+
+ JobTaskConverter INSTANCE = Mappers.getMapper(JobTaskConverter.class);
+
+ @Mappings(
+ @Mapping(source = "id", target = "jobId")
+ )
+ JobTaskPrepareDTO toJobTaskPrepare(JobPartitionTask job);
+
+ JobTaskBatchGeneratorContext toJobTaskGeneratorContext(JobTaskPrepareDTO jobTaskPrepareDTO);
+
+ JobTaskBatchGeneratorContext toJobTaskGeneratorContext(BlockStrategies.BlockStrategyContext context);
+
+ JobTaskGenerateContext toJobTaskInstanceGenerateContext(JobExecutorContext context);
+
+ JobTask toJobTaskInstance(JobTaskGenerateContext context);
+
+ BlockStrategies.BlockStrategyContext toBlockStrategyContext(JobTaskPrepareDTO prepareDTO);
+
+ TaskStopJobContext toStopJobContext(BlockStrategies.BlockStrategyContext context);
+
+ JobLogMessage toJobLogMessage(JobLogDTO jobLogDTO);
+
+ JobLogDTO toJobLogDTO(JobExecutorContext context);
+
+ JobLogDTO toJobLogDTO(JobExecutorResultDTO resultDTO);
+
+ JobLogDTO toJobLogDTO(BaseDTO baseDTO);
+
+ JobLogDTO toJobLogDTO(DispatchJobResultRequest request);
+
+ ClientCallbackContext toClientCallbackContext(DispatchJobResultRequest request);
+
+ DispatchJobRequest toDispatchJobRequest(RealJobExecutorDTO realJobExecutorDTO);
+
+ @Mappings({
+ @Mapping(source = "job.groupName", target = "groupName"),
+ @Mapping(source = "jobTask.id", target = "taskId"),
+ @Mapping(source = "jobTask.argsStr", target = "argsStr"),
+ @Mapping(source = "jobTask.argsType", target = "argsType"),
+ @Mapping(source = "jobTask.extAttrs", target = "extAttrs")
+ })
+ RealJobExecutorDTO toRealJobExecutorDTO(Job job, JobTask jobTask);
+
+ JobExecutorResultDTO toJobExecutorResultDTO(ClientCallbackContext context);
+
+ JobExecutorResultDTO toJobExecutorResultDTO(JobTask jobTask);
+
+ RealStopTaskInstanceDTO toRealStopTaskInstanceDTO(TaskStopJobContext context);
+
+ List toJobPartitionTasks(List jobs);
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobExecutorActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobExecutorActor.java
new file mode 100644
index 00000000..8ec3f137
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobExecutorActor.java
@@ -0,0 +1,55 @@
+package com.aizuda.easy.retry.server.job.task.dispatch;
+
+import akka.actor.AbstractActor;
+import com.aizuda.easy.retry.common.core.log.LogUtils;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.dto.TaskExecuteDTO;
+import com.aizuda.easy.retry.server.job.task.handler.executor.JobExecutor;
+import com.aizuda.easy.retry.server.job.task.handler.executor.JobExecutorContext;
+import com.aizuda.easy.retry.server.job.task.handler.executor.JobExecutorFactory;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-09-25 17:41
+ * @since : 2.4.0
+ */
+@Component(ActorGenerator.JOB_EXECUTOR_ACTOR)
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+@Slf4j
+public class JobExecutorActor extends AbstractActor {
+ @Autowired
+ private JobMapper jobMapper;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder().match(TaskExecuteDTO.class, taskExecute -> {
+ try {
+ doExecute(taskExecute);
+ } catch (Exception e) {
+ LogUtils.error(log, "job executor exception. [{}]", taskExecute, e);
+ } finally {
+ getContext().stop(getSelf());
+ }
+ }).build();
+ }
+
+ private void doExecute(final TaskExecuteDTO taskExecute) {
+ Job job = jobMapper.selectById(taskExecute.getJobId());
+ JobExecutor jobExecutor = JobExecutorFactory.getJobExecutor(job.getTaskType());
+
+ JobExecutorContext context = new JobExecutorContext();
+ context.setTaskBatchId(taskExecute.getTaskBatchId());
+ context.setGroupName(taskExecute.getGroupName());
+ context.setJobId(job.getId());
+ context.setTaskType(job.getTaskType());
+ jobExecutor.execute(context);
+
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobExecutorResultActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobExecutorResultActor.java
new file mode 100644
index 00000000..6c4f0749
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobExecutorResultActor.java
@@ -0,0 +1,76 @@
+package com.aizuda.easy.retry.server.job.task.dispatch;
+
+import akka.actor.AbstractActor;
+import akka.actor.ActorRef;
+import cn.hutool.core.lang.Assert;
+import com.aizuda.easy.retry.common.core.util.JsonUtil;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobExecutorResultDTO;
+import com.aizuda.easy.retry.server.job.task.dto.JobLogDTO;
+import com.aizuda.easy.retry.server.job.task.handler.helper.JobTaskBatchHelper;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskBatchMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import java.util.Objects;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-05 17:16:35
+ * @since 2.4.0
+ */
+@Component(ActorGenerator.JOB_EXECUTOR_RESULT_ACTOR)
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+@Slf4j
+public class JobExecutorResultActor extends AbstractActor {
+
+ @Autowired
+ private JobTaskMapper jobTaskMapper;
+ @Autowired
+ private TransactionTemplate transactionTemplate;
+ @Autowired
+ private JobTaskBatchHelper jobTaskBatchHelper;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder().match(JobExecutorResultDTO.class, result -> {
+
+ transactionTemplate.execute(new TransactionCallbackWithoutResult() {
+ @Override
+ protected void doInTransactionWithoutResult(final TransactionStatus status) {
+ JobTask jobTask = new JobTask();
+ jobTask.setExecuteStatus(result.getTaskStatus());
+ if (Objects.nonNull(result.getResult())) {
+ jobTask.setResultMessage(JsonUtil.toJsonString(result.getResult()));
+ }
+
+ Assert.isTrue(1 == jobTaskMapper.update(jobTask,
+ new LambdaUpdateWrapper().eq(JobTask::getId, result.getTaskId())),
+ ()-> new EasyRetryServerException("更新任务实例失败"));
+
+ // 更新批次上的状态
+ jobTaskBatchHelper.complete(result.getTaskBatchId());
+ }
+ });
+
+ JobLogDTO jobLogDTO = JobTaskConverter.INSTANCE.toJobLogDTO(result);
+ jobLogDTO.setMessage(result.getMessage());
+ jobLogDTO.setClientId(result.getClientId());
+ jobLogDTO.setTaskId(result.getTaskId());
+ ActorRef actorRef = ActorGenerator.jobLogActor();
+ actorRef.tell(jobLogDTO, actorRef);
+
+ }).build();
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobLogActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobLogActor.java
new file mode 100644
index 00000000..bc4bc5ba
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobLogActor.java
@@ -0,0 +1,61 @@
+package com.aizuda.easy.retry.server.job.task.dispatch;
+
+import akka.actor.AbstractActor;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobLogDTO;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobLogMessageMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobLogMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 22:32:30
+ * @since 2.4.0
+ */
+@Component(ActorGenerator.JOB_LOG_ACTOR)
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+@Slf4j
+public class JobLogActor extends AbstractActor {
+
+ @Autowired
+ private JobLogMessageMapper jobLogMessageMapper;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder().match(JobLogDTO.class, (jobLogDTO -> {
+ try {
+ saveLogMessage(jobLogDTO);
+ } catch (Exception e) {
+ log.error("保存日志异常.", e);
+ } finally {
+ getContext().stop(getSelf());
+ }
+ })).build();
+
+ }
+
+ private void saveLogMessage(JobLogDTO jobLogDTO) {
+ JobLogMessage jobLogMessage = JobTaskConverter.INSTANCE.toJobLogMessage(jobLogDTO);
+ if (Objects.nonNull(jobLogDTO.getClientId())) {
+ Optional.ofNullable(CacheRegisterTable.getServerNode(jobLogDTO.getGroupName(), jobLogDTO.getClientId())).ifPresent(registerNodeInfo -> {
+ jobLogMessage.setClientAddress(registerNodeInfo.fullUrl());
+ });
+ }
+
+ jobLogMessage.setCreateDt(LocalDateTime.now());
+ jobLogMessage.setMessage(Optional.ofNullable(jobLogDTO.getMessage()).orElse(StrUtil.EMPTY));
+ jobLogMessage.setTaskId(Optional.ofNullable(jobLogMessage.getTaskId()).orElse(0L));
+ jobLogMessageMapper.insert(jobLogMessage);
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobTaskPrepareActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobTaskPrepareActor.java
new file mode 100644
index 00000000..61341b96
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/JobTaskPrepareActor.java
@@ -0,0 +1,75 @@
+package com.aizuda.easy.retry.server.job.task.dispatch;
+
+import akka.actor.AbstractActor;
+import com.aizuda.easy.retry.common.core.context.SpringContext;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
+import com.aizuda.easy.retry.server.job.task.handler.prepare.JobPrePareHandler;
+import com.aizuda.easy.retry.server.job.task.handler.prepare.TerminalJobPrepareHandler;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskBatchMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import static com.aizuda.easy.retry.common.core.enums.JobTaskBatchStatusEnum.NOT_COMPLETE;
+
+/**
+ * 调度任务准备阶段
+ *
+ * @author www.byteblogs.com
+ * @date 2023-09-25 22:20:53
+ * @since
+ */
+@Component(ActorGenerator.JOB_TASK_PREPARE_ACTOR)
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+@Slf4j
+public class JobTaskPrepareActor extends AbstractActor {
+
+ @Autowired
+ private JobTaskBatchMapper jobTaskBatchMapper;
+ @Autowired
+ private List prePareHandlers;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder().match(JobTaskPrepareDTO.class, job -> {
+ try {
+ doPrepare(job);
+ } catch (Exception e) {
+ log.error("预处理节点异常", e);
+ } finally {
+ getContext().stop(getSelf());
+ }
+ }).build();
+ }
+
+ private void doPrepare(JobTaskPrepareDTO prepare) {
+
+ List notCompleteJobTaskBatchList = jobTaskBatchMapper.selectList(new LambdaQueryWrapper()
+ .eq(JobTaskBatch::getJobId, prepare.getJobId())
+ .in(JobTaskBatch::getTaskStatus, NOT_COMPLETE));
+
+ // 说明所以任务已经完成
+ if (CollectionUtils.isEmpty(notCompleteJobTaskBatchList)) {
+ TerminalJobPrepareHandler terminalJobPrepareHandler = SpringContext.getBeanByType(TerminalJobPrepareHandler.class);
+ terminalJobPrepareHandler.handler(prepare);
+ } else {
+ for (JobTaskBatch jobTaskBatch : notCompleteJobTaskBatchList) {
+ prepare.setExecutionAt(jobTaskBatch.getExecutionAt());
+ prepare.setTaskBatchId(jobTaskBatch.getId());
+ for (JobPrePareHandler prePareHandler : prePareHandlers) {
+ if (prePareHandler.matches(jobTaskBatch.getTaskStatus())) {
+ prePareHandler.handler(prepare);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/ScanJobTaskActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/ScanJobTaskActor.java
new file mode 100644
index 00000000..13bd2680
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dispatch/ScanJobTaskActor.java
@@ -0,0 +1,137 @@
+package com.aizuda.easy.retry.server.job.task.dispatch;
+
+import akka.actor.AbstractActor;
+import akka.actor.ActorRef;
+import cn.hutool.core.lang.Assert;
+import com.aizuda.easy.retry.common.core.enums.StatusEnum;
+import com.aizuda.easy.retry.common.core.log.LogUtils;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.cache.CacheConsumerGroup;
+import com.aizuda.easy.retry.server.common.dto.PartitionTask;
+import com.aizuda.easy.retry.server.common.dto.ScanTask;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.WaitStrategy;
+import com.aizuda.easy.retry.server.job.task.dto.JobPartitionTask;
+import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static com.aizuda.easy.retry.server.job.task.strategy.WaitStrategies.*;
+
+/**
+ * JOB任务扫描
+ *
+ * @author: www.byteblogs.com
+ * @date : 2023-09-22 09:08
+ * @since 2.4.0
+ */
+@Component(ActorGenerator.SCAN_JOB_ACTOR)
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+@Slf4j
+public class ScanJobTaskActor extends AbstractActor {
+
+ @Autowired
+ private JobMapper jobMapper;
+
+ private static final AtomicLong lastId = new AtomicLong(0L);
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder().match(ScanTask.class, config -> {
+
+ try {
+ doScan(config);
+ } catch (Exception e) {
+ LogUtils.error(log, "Data scanner processing exception. [{}]", config, e);
+ }
+
+ }).build();
+
+ }
+
+ private void doScan(final ScanTask scanTask) {
+ log.info("job scan start");
+
+ long total = process(startId -> listAvailableJobs(startId, scanTask), partitionTasks -> {
+ for (final JobPartitionTask partitionTask : (List) partitionTasks) {
+ CacheConsumerGroup.addOrUpdate(partitionTask.getGroupName());
+ JobTaskPrepareDTO jobTaskPrepare = JobTaskConverter.INSTANCE.toJobTaskPrepare(partitionTask);
+
+ // 更新下次触发时间
+ WaitStrategy waitStrategy = WaitStrategyEnum.getWaitStrategy(partitionTask.getTriggerType());
+ WaitStrategyContext waitStrategyContext = new WaitStrategyContext();
+ waitStrategyContext.setTriggerType(partitionTask.getTriggerType());
+ waitStrategyContext.setTriggerInterval(partitionTask.getTriggerInterval());
+ waitStrategyContext.setNextTriggerAt(partitionTask.getNextTriggerAt());
+
+ Job job = new Job();
+ job.setId(partitionTask.getId());
+ job.setNextTriggerAt(waitStrategy.computeRetryTime(waitStrategyContext));
+ Assert.isTrue(1 == jobMapper.updateById(job),
+ () -> new EasyRetryServerException("更新job下次触发时间失败.jobId:[{}]", job.getId()));
+
+ // 执行预处理阶段
+ ActorRef actorRef = ActorGenerator.jobTaskPrepareActor();
+ actorRef.tell(jobTaskPrepare, actorRef);
+ }
+ }, 0);
+
+ log.info("job scan end. total:[{}]", total);
+ }
+
+ private List listAvailableJobs(Long startId, ScanTask scanTask) {
+
+ List jobs = jobMapper.selectPage(new PageDTO(0, 1000),
+ new LambdaQueryWrapper()
+ .eq(Job::getJobStatus, StatusEnum.YES.getStatus())
+ .in(Job::getBucketIndex, scanTask.getBuckets())
+ .le(Job::getNextTriggerAt, LocalDateTime.now().plusSeconds(6))
+ .eq(Job::getDeleted, StatusEnum.NO.getStatus())
+ .gt(Job::getId, startId)
+ ).getRecords();
+
+ return JobTaskConverter.INSTANCE.toJobPartitionTasks(jobs);
+ }
+
+ public long process(
+ Function> dataSource, Consumer> task, long startId) {
+ int total = 0;
+ do {
+ List extends PartitionTask> products = dataSource.apply(startId);
+ if (CollectionUtils.isEmpty(products)) {
+ // 没有查询到数据直接退出
+ break;
+ }
+
+ total += products.size();
+
+ task.accept(products);
+ startId = maxId(products);
+ } while (startId > 0);
+
+ return total;
+ }
+
+ private static long maxId(List extends PartitionTask> products) {
+ Optional max = products.stream().map(PartitionTask::getId).max(Long::compareTo);
+ return max.orElse(-1L) + 1;
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/BaseDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/BaseDTO.java
new file mode 100644
index 00000000..75186bed
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/BaseDTO.java
@@ -0,0 +1,19 @@
+package com.aizuda.easy.retry.server.job.task.dto;
+
+import lombok.Data;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-06 17:05:04
+ * @since 2.4.0
+ */
+@Data
+public class BaseDTO {
+
+ private Long jobId;
+ private Long taskBatchId;
+ private String groupName;
+ private Long taskId;
+ private Integer taskType;
+ private String clientId;
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobExecutorResultDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobExecutorResultDTO.java
new file mode 100644
index 00000000..26cb15d9
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobExecutorResultDTO.java
@@ -0,0 +1,31 @@
+package com.aizuda.easy.retry.server.job.task.dto;
+
+import lombok.Data;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-05 17:18:38
+ * @since 2.4.0
+ */
+@Data
+public class JobExecutorResultDTO {
+
+ private Long jobId;
+
+ private Long taskBatchId;
+
+ private Long taskId;
+
+ private String groupName;
+
+ private Integer taskStatus;
+
+ private String message;
+
+ private String clientId;
+
+ private Integer taskType;
+
+ private Object result;
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobLogDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobLogDTO.java
new file mode 100644
index 00000000..8809d390
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobLogDTO.java
@@ -0,0 +1,44 @@
+package com.aizuda.easy.retry.server.job.task.dto;
+
+import lombok.Data;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 22:34:14
+ * @since 2.4.0
+ */
+@Data
+public class JobLogDTO {
+
+ /**
+ * 组名称
+ */
+ private String groupName;
+
+ /**
+ * 任务信息id
+ */
+ private Long jobId;
+
+ /**
+ * 任务实例id
+ */
+ private Long taskBatchId;
+
+ /**
+ * 调度任务id
+ */
+ private Long taskId;
+
+ /**
+ * 执行的客户端信息
+ */
+ private String clientId;
+
+ /**
+ * 调度信息
+ */
+ private String message;
+
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobPartitionTask.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobPartitionTask.java
new file mode 100644
index 00000000..1012de9d
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobPartitionTask.java
@@ -0,0 +1,52 @@
+package com.aizuda.easy.retry.server.job.task.dto;
+
+import com.aizuda.easy.retry.server.common.dto.PartitionTask;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-10-10 17:52
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class JobPartitionTask extends PartitionTask {
+
+ /**
+ * 组名称
+ */
+ private String groupName;
+
+ /**
+ * 名称
+ */
+ private String jobName;
+
+ /**
+ * 下次触发时间
+ */
+ private LocalDateTime nextTriggerAt;
+
+ /**
+ * 阻塞策略 1、丢弃 2、覆盖 3、并行
+ */
+ private Integer blockStrategy;
+
+ /**
+ * 触发类型 1.CRON 表达式 2. 固定时间
+ */
+ private Integer triggerType;
+
+ /**
+ * 间隔时长
+ */
+ private String triggerInterval;
+
+ /**
+ * 任务执行超时时间,单位秒
+ */
+ private Integer executorTimeout;
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobTaskPrepareDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobTaskPrepareDTO.java
index 702b462e..635c26ea 100644
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobTaskPrepareDTO.java
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobTaskPrepareDTO.java
@@ -24,79 +24,33 @@ public class JobTaskPrepareDTO {
*/
private String jobName;
- /**
- * 执行方法参数
- */
- private String argsStr;
-
- /**
- * 参数类型 text/json
- */
- private String argsType;
-
- /**
- * 扩展字段
- */
- private String extAttrs;
-
/**
* 下次触发时间
*/
private LocalDateTime nextTriggerAt;
- /**
- * 重试状态 0、关闭、1、开启
- */
- private Integer jobStatus;
-
- /**
- * 执行器路由策略
- */
- private String routeKey;
-
- /**
- * 执行器类型 1、Java
- */
- private Integer executorType;
-
- /**
- * 执行器名称
- */
- private String executorName;
-
- /**
- * 触发类型 1.CRON 表达式 2. 固定时间
- */
- private Integer triggerType;
-
- /**
- * 间隔时长
- */
- private String triggerInterval;
-
/**
* 阻塞策略 1、丢弃 2、覆盖 3、并行
*/
private Integer blockStrategy;
+ /**
+ * 任务类型
+ */
+ private Integer taskType;
+
/**
* 任务执行超时时间,单位秒
*/
private Integer executorTimeout;
- /**
- * 最大重试次数
- */
- private Integer maxRetryTimes;
+ private Long taskBatchId;
+
+ private String clientId;
/**
- * 重试间隔(s)
+ * 任务执行时间
*/
- private Integer retryInterval;
-
- /**
- * bucket
- */
- private Integer bucketIndex;
+ private LocalDateTime executionAt;
}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobTimerTaskDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobTimerTaskDTO.java
new file mode 100644
index 00000000..55f7f1f8
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/JobTimerTaskDTO.java
@@ -0,0 +1,14 @@
+package com.aizuda.easy.retry.server.job.task.dto;
+
+import lombok.Data;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-09-30 23:19:39
+ * @since 2.4.0
+ */
+@Data
+public class JobTimerTaskDTO {
+
+ private Long taskBatchId;
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/RealJobExecutorDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/RealJobExecutorDTO.java
new file mode 100644
index 00000000..027cdf07
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/RealJobExecutorDTO.java
@@ -0,0 +1,71 @@
+package com.aizuda.easy.retry.server.job.task.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-06 16:45:13
+ * @since 2.4.0
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class RealJobExecutorDTO extends BaseDTO {
+
+ private Long jobId;
+
+ /**
+ * 名称
+ */
+ private String jobName;
+
+ /**
+ * 执行方法参数
+ */
+ private String argsStr;
+
+ /**
+ * 参数类型 text/json
+ */
+ private String argsType;
+
+ /**
+ * 扩展字段
+ */
+ private String extAttrs;
+
+
+ private Long taskBatchId;
+
+ private Long taskId;
+
+ private Integer taskType;
+
+ private String groupName;
+
+ private Integer parallelNum;
+
+ private Integer executorType;
+
+ private String executorName;
+
+ private String clientId;
+
+ /**
+ * 最大重试次数
+ */
+ private Integer maxRetryTimes;
+
+ /**
+ * 重试间隔(s)
+ */
+ private Integer retryInterval;
+
+ private Integer shardingTotal;
+
+ private Integer shardingIndex;
+
+ private Integer executorTimeout;
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/RealStopTaskInstanceDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/RealStopTaskInstanceDTO.java
new file mode 100644
index 00000000..dc20f0d5
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/RealStopTaskInstanceDTO.java
@@ -0,0 +1,20 @@
+package com.aizuda.easy.retry.server.job.task.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-10-07 10:50
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class RealStopTaskInstanceDTO extends BaseDTO {
+
+ /**
+ * 下次触发时间
+ */
+ private LocalDateTime nextTriggerAt;
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/TaskExecuteDTO.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/TaskExecuteDTO.java
index 6b9b6e48..ab8f15ed 100644
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/TaskExecuteDTO.java
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/dto/TaskExecuteDTO.java
@@ -9,5 +9,7 @@ import lombok.Data;
@Data
public class TaskExecuteDTO {
- private Long taskId;
+ private Long jobId;
+ private Long taskBatchId;
+ private String groupName;
}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/BlockStrategyEnum.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/BlockStrategyEnum.java
deleted file mode 100644
index be3ef171..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/BlockStrategyEnum.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.enums;
-
-/**
- * @author www.byteblogs.com
- * @date 2023-09-24 12:01:31
- * @since 2.4.0
- */
-public enum BlockStrategyEnum {
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/TaskStatusEnum.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/TaskStatusEnum.java
deleted file mode 100644
index 109c0f77..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/TaskStatusEnum.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.enums;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * @author: www.byteblogs.com
- * @date : 2023-09-26 14:26
- */
-@AllArgsConstructor
-@Getter
-public enum TaskStatusEnum {
-
- /**
- * 待处理
- */
- WAIT(10),
-
- /**
- * 处理中
- */
- PROCESSING(20),
-
- /**
- * 处理中
- */
- PROCESSED_SUCCESS(21),
-
- /**
- * 处理中
- */
- PROCESSED_FAIL(22),
-
- /**
- * 中断中
- */
- INTERRUPTING(30),
-
- /**
- * 中断成功
- */
- INTERRUPT_SUCCESS(31),
-
- /**
- * 中断失败
- */
- INTERRUPT_FAIL(32),
-
- ;
-
- private final int status;
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/TaskTypeEnum.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/TaskTypeEnum.java
new file mode 100644
index 00000000..f36681db
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/enums/TaskTypeEnum.java
@@ -0,0 +1,31 @@
+package com.aizuda.easy.retry.server.job.task.enums;
+
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 10:39:22
+ * @since 2.4.0
+ */
+@AllArgsConstructor
+@Getter
+public enum TaskTypeEnum {
+
+ CLUSTER(1),
+ BROADCAST(2),
+ SHARDING(3);
+
+ private final int type;
+
+ public static TaskTypeEnum valueOf(int type) {
+ for (TaskTypeEnum value : TaskTypeEnum.values()) {
+ if (value.getType() == type) {
+ return value;
+ }
+ }
+
+ throw new EasyRetryServerException("未知类型");
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/executor/JobExecutor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/executor/JobExecutor.java
deleted file mode 100644
index 687a5545..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/executor/JobExecutor.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.executor;
-
-/**
- * @author www.byteblogs.com
- * @date 2023-09-24 11:40:21
- * @since 2.4.0
- */
-public class JobExecutor {
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/batch/JobTaskBatchGenerator.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/batch/JobTaskBatchGenerator.java
new file mode 100644
index 00000000..5275addc
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/batch/JobTaskBatchGenerator.java
@@ -0,0 +1,65 @@
+package com.aizuda.easy.retry.server.job.task.generator.batch;
+
+import cn.hutool.core.lang.Assert;
+import com.aizuda.easy.retry.common.core.enums.JobOperationReasonEnum;
+import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.job.task.dto.JobTimerTaskDTO;
+import com.aizuda.easy.retry.common.core.enums.JobTaskBatchStatusEnum;
+import com.aizuda.easy.retry.server.job.task.handler.timer.JobTimerTask;
+import com.aizuda.easy.retry.server.job.task.handler.timer.JobTimerWheelHandler;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskBatchMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.time.ZoneId;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 10:22:26
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class JobTaskBatchGenerator {
+
+ @Autowired
+ private JobTaskBatchMapper jobTaskBatchMapper;
+
+ @Transactional
+ public void generateJobTaskBatch(JobTaskBatchGeneratorContext context) {
+
+ // 生成一个新的任务
+ JobTaskBatch jobTaskBatch = new JobTaskBatch();
+ jobTaskBatch.setJobId(context.getJobId());
+ jobTaskBatch.setGroupName(context.getGroupName());
+
+ // 无执行的节点
+ if (CollectionUtils.isEmpty(CacheRegisterTable.getServerNodeSet(context.getGroupName()))) {
+ jobTaskBatch.setTaskStatus(JobTaskBatchStatusEnum.CANCEL.getStatus());
+ jobTaskBatch.setOperationReason(JobOperationReasonEnum.NOT_CLIENT.getReason());
+ Assert.isTrue(1 == jobTaskBatchMapper.insert(jobTaskBatch), () -> new EasyRetryServerException("新增调度任务失败.jobId:[{}]", context.getJobId()));
+ return;
+ }
+
+ // 生成一个新的任务
+ jobTaskBatch.setTaskStatus(JobTaskBatchStatusEnum.WAITING.getStatus());
+ Assert.isTrue(1 == jobTaskBatchMapper.insert(jobTaskBatch), () -> new EasyRetryServerException("新增调度任务失败.jobId:[{}]", context.getJobId()));
+
+ // 进入时间轮
+ long delay = context.getNextTriggerAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
+ - System.currentTimeMillis();
+ JobTimerTaskDTO jobTimerTaskDTO = new JobTimerTaskDTO();
+ jobTimerTaskDTO.setTaskBatchId(jobTaskBatch.getId());
+
+ JobTimerWheelHandler.register(context.getGroupName(), context.getJobId(),
+ new JobTimerTask(jobTimerTaskDTO), delay, TimeUnit.MILLISECONDS);
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/batch/JobTaskBatchGeneratorContext.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/batch/JobTaskBatchGeneratorContext.java
new file mode 100644
index 00000000..95adc7d3
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/batch/JobTaskBatchGeneratorContext.java
@@ -0,0 +1,36 @@
+package com.aizuda.easy.retry.server.job.task.generator.batch;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 13:12:48
+ * @since 2.4.0
+ */
+@Data
+public class JobTaskBatchGeneratorContext {
+
+ /**
+ * 组名称
+ */
+ private String groupName;
+
+ /**
+ * 任务id
+ */
+ private Long jobId;
+
+ /**
+ * 任务类型
+ */
+ private Integer taskInstanceType;
+
+ /**
+ * 下次触发时间
+ */
+ private LocalDateTime nextTriggerAt;
+
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/AbstractJobTaskGenerator.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/AbstractJobTaskGenerator.java
new file mode 100644
index 00000000..548dab4d
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/AbstractJobTaskGenerator.java
@@ -0,0 +1,26 @@
+package com.aizuda.easy.retry.server.job.task.generator.task;
+
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import org.springframework.beans.factory.InitializingBean;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 13:08:14
+ * @since 2.4.0
+ */
+public abstract class AbstractJobTaskGenerator implements JobTaskGenerator, InitializingBean {
+
+ @Override
+ public List generate(JobTaskGenerateContext context) {
+ return doGenerate(context);
+ }
+
+ protected abstract List doGenerate(JobTaskGenerateContext context);
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ JobTaskGeneratorFactory.registerTaskInstance(getTaskInstanceType(), this);
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/BroadcastTaskGenerator.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/BroadcastTaskGenerator.java
new file mode 100644
index 00000000..cb0ef15d
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/BroadcastTaskGenerator.java
@@ -0,0 +1,73 @@
+package com.aizuda.easy.retry.server.job.task.generator.task;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.easy.retry.common.core.enums.JobTaskStatusEnum;
+import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
+import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 21:25:08
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class BroadcastTaskGenerator extends AbstractJobTaskGenerator {
+
+ @Autowired
+ private JobTaskMapper jobTaskMapper;
+ @Autowired
+ private JobMapper jobMapper;
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.BROADCAST;
+ }
+
+ @Override
+ @Transactional
+ public List doGenerate(JobTaskGenerateContext context) {
+ Set serverNodes = CacheRegisterTable.getServerNodeSet(context.getGroupName());
+ if (CollectionUtils.isEmpty(serverNodes)) {
+ log.error("无可执行的客户端信息. jobId:[{}]", context.getJobId());
+ return Lists.newArrayList();
+ }
+
+ Job job = jobMapper.selectById(context.getJobId());
+
+ List jobTasks = new ArrayList<>(serverNodes.size());
+ for (RegisterNodeInfo serverNode : serverNodes) {
+ JobTask jobTask = JobTaskConverter.INSTANCE.toJobTaskInstance(context);
+ jobTask.setClientId(serverNode.getHostId());
+ jobTask.setArgsType(job.getArgsType());
+ jobTask.setArgsStr(job.getArgsStr());
+ jobTask.setExtAttrs(job.getExtAttrs());
+ jobTask.setExecuteStatus(JobTaskStatusEnum.RUNNING.getStatus());
+ jobTask.setResultMessage(Optional.ofNullable(jobTask.getResultMessage()).orElse(StrUtil.EMPTY));
+ Assert.isTrue(1 == jobTaskMapper.insert(jobTask), () -> new EasyRetryServerException("新增任务实例失败"));
+ jobTasks.add(jobTask);
+ }
+
+ return jobTasks;
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/ClusterTaskGenerator.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/ClusterTaskGenerator.java
new file mode 100644
index 00000000..6f05c85e
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/ClusterTaskGenerator.java
@@ -0,0 +1,69 @@
+package com.aizuda.easy.retry.server.job.task.generator.task;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.easy.retry.common.core.enums.JobTaskStatusEnum;
+import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.common.handler.ClientNodeAllocateHandler;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 12:59:53
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class ClusterTaskGenerator extends AbstractJobTaskGenerator {
+
+ @Autowired
+ protected ClientNodeAllocateHandler clientNodeAllocateHandler;
+ @Autowired
+ private JobTaskMapper jobTaskMapper;
+ @Autowired
+ private JobMapper jobMapper;
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.CLUSTER;
+ }
+
+ @Override
+ public List doGenerate(JobTaskGenerateContext context) {
+ // 生成可执行任务
+ RegisterNodeInfo serverNode = clientNodeAllocateHandler.getServerNode(context.getGroupName());
+ if (Objects.isNull(serverNode)) {
+ log.error("无可执行的客户端信息. jobId:[{}]", context.getJobId());
+ return Lists.newArrayList();
+ }
+
+ Job job = jobMapper.selectById(context.getJobId());
+
+ // 新增任务实例
+ JobTask jobTask = JobTaskConverter.INSTANCE.toJobTaskInstance(context);
+ jobTask.setClientId(serverNode.getHostId());
+ jobTask.setArgsType(job.getArgsType());
+ jobTask.setArgsStr(job.getArgsStr());
+ jobTask.setExtAttrs(job.getExtAttrs());
+ jobTask.setExecuteStatus(JobTaskStatusEnum.RUNNING.getStatus());
+ jobTask.setResultMessage(Optional.ofNullable(jobTask.getResultMessage()).orElse(StrUtil.EMPTY));
+ Assert.isTrue(1 == jobTaskMapper.insert(jobTask), () -> new EasyRetryServerException("新增任务实例失败"));
+
+ return Lists.newArrayList(jobTask);
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGenerateContext.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGenerateContext.java
new file mode 100644
index 00000000..75eae9d3
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGenerateContext.java
@@ -0,0 +1,15 @@
+package com.aizuda.easy.retry.server.job.task.generator.task;
+
+import lombok.Data;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 13:02:57
+ * @since 2.4.0
+ */
+@Data
+public class JobTaskGenerateContext {
+ private Long taskBatchId;
+ private String groupName;
+ private Long jobId;
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGenerator.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGenerator.java
new file mode 100644
index 00000000..8e0f0031
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGenerator.java
@@ -0,0 +1,19 @@
+package com.aizuda.easy.retry.server.job.task.generator.task;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 10:43:58
+ * @since 2.4.0
+ */
+public interface JobTaskGenerator {
+
+ TaskTypeEnum getTaskInstanceType();
+
+ List generate(JobTaskGenerateContext context);
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGeneratorFactory.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGeneratorFactory.java
new file mode 100644
index 00000000..ef08a0b8
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/JobTaskGeneratorFactory.java
@@ -0,0 +1,23 @@
+package com.aizuda.easy.retry.server.job.task.generator.task;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 13:04:09
+ * @since 2.4.0
+ */
+public class JobTaskGeneratorFactory {
+
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ public static void registerTaskInstance(TaskTypeEnum taskInstanceType, JobTaskGenerator generator) {
+ CACHE.put(taskInstanceType, generator);
+ }
+
+ public static JobTaskGenerator getTaskInstance(Integer type) {
+ return CACHE.get(TaskTypeEnum.valueOf(type));
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/ShardingTaskGenerator.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/ShardingTaskGenerator.java
new file mode 100644
index 00000000..e34705f6
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/generator/task/ShardingTaskGenerator.java
@@ -0,0 +1,80 @@
+package com.aizuda.easy.retry.server.job.task.generator.task;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.aizuda.easy.retry.common.core.enums.JobTaskStatusEnum;
+import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
+import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.common.handler.ClientNodeAllocateHandler;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+/**
+ * 分片参数格式
+ * 0=参数1;1=参数2;
+ *
+ * @author www.byteblogs.com
+ * @date 2023-10-02 21:37:22
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class ShardingTaskGenerator extends AbstractJobTaskGenerator {
+
+ @Autowired
+ private JobMapper jobMapper;
+ @Autowired
+ protected ClientNodeAllocateHandler clientNodeAllocateHandler;
+ @Autowired
+ private JobTaskMapper jobTaskMapper;
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.SHARDING;
+ }
+
+ @Override
+ public List doGenerate(JobTaskGenerateContext context) {
+
+ Set serverNodes = CacheRegisterTable.getServerNodeSet(context.getGroupName());
+ if (CollectionUtils.isEmpty(serverNodes)) {
+ log.error("无可执行的客户端信息. jobId:[{}]", context.getJobId());
+ return Lists.newArrayList();
+ }
+
+ Job job = jobMapper.selectById(context.getJobId());
+ String argsStr = job.getArgsStr();
+ Map split = Splitter.on(";").omitEmptyStrings().withKeyValueSeparator('=').split(argsStr);
+
+ List nodeInfoList = new ArrayList<>(serverNodes);
+ List jobTasks = new ArrayList<>(split.size());
+ split.forEach((key, value) -> {
+ RegisterNodeInfo registerNodeInfo = nodeInfoList.get(Integer.parseInt(key) % serverNodes.size());
+ // 新增任务实例
+ JobTask jobTask = JobTaskConverter.INSTANCE.toJobTaskInstance(context);
+ jobTask.setClientId(registerNodeInfo.getHostId());
+ jobTask.setArgsType(job.getArgsType());
+ jobTask.setArgsStr(job.getArgsStr());
+ jobTask.setExtAttrs(job.getExtAttrs());
+ jobTask.setExecuteStatus(JobTaskStatusEnum.RUNNING.getStatus());
+ jobTask.setResultMessage(Optional.ofNullable(jobTask.getResultMessage()).orElse(StrUtil.EMPTY));
+ Assert.isTrue(1 == jobTaskMapper.insert(jobTask), () -> new EasyRetryServerException("新增任务实例失败"));
+ });
+
+ return jobTasks;
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/AbstractClientCallbackHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/AbstractClientCallbackHandler.java
new file mode 100644
index 00000000..0e52b769
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/AbstractClientCallbackHandler.java
@@ -0,0 +1,25 @@
+package com.aizuda.easy.retry.server.job.task.handler.callback;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 23:12:33
+ * @since 2.4.0
+ */
+public abstract class AbstractClientCallbackHandler implements ClientCallbackHandler, InitializingBean {
+
+ @Override
+ @Transactional
+ public void callback(ClientCallbackContext context) {
+ doCallback(context);
+ }
+
+ protected abstract void doCallback(ClientCallbackContext context);
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ ClientCallbackFactory.registerJobExecutor(getTaskInstanceType(), this);
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/BroadcastClientCallbackHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/BroadcastClientCallbackHandler.java
new file mode 100644
index 00000000..69e6c3ac
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/BroadcastClientCallbackHandler.java
@@ -0,0 +1,40 @@
+package com.aizuda.easy.retry.server.job.task.handler.callback;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobExecutorResultDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-10-07 10:24
+ * @since : 2.4.0
+ */
+@Component
+@Slf4j
+public class BroadcastClientCallbackHandler extends AbstractClientCallbackHandler {
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.BROADCAST;
+ }
+
+ @Override
+ protected void doCallback(final ClientCallbackContext context) {
+ JobExecutorResultDTO jobExecutorResultDTO = JobTaskConverter.INSTANCE.toJobExecutorResultDTO(context);
+ jobExecutorResultDTO.setTaskId(context.getTaskId());
+ jobExecutorResultDTO.setMessage(context.getExecuteResult().getMessage());
+ jobExecutorResultDTO.setResult(context.getExecuteResult().getResult());
+ jobExecutorResultDTO.setTaskType(getTaskInstanceType().getType());
+
+ ActorRef actorRef = ActorGenerator.jobTaskExecutorResultActor();
+ actorRef.tell(jobExecutorResultDTO, actorRef);
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackContext.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackContext.java
new file mode 100644
index 00000000..4da00a90
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackContext.java
@@ -0,0 +1,27 @@
+package com.aizuda.easy.retry.server.job.task.handler.callback;
+
+import com.aizuda.easy.retry.client.model.ExecuteResult;
+import lombok.Data;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 23:13:05
+ * @since 2.4.0
+ */
+@Data
+public class ClientCallbackContext {
+
+ private Long jobId;
+
+ private Long taskBatchId;
+
+ private Long taskId;
+
+ private String groupName;
+
+ private Integer taskStatus;
+
+ private ExecuteResult executeResult;
+
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackFactory.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackFactory.java
new file mode 100644
index 00000000..d5f947d7
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackFactory.java
@@ -0,0 +1,23 @@
+package com.aizuda.easy.retry.server.job.task.handler.callback;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 13:04:09
+ * @since 2.4.0
+ */
+public class ClientCallbackFactory {
+
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ public static void registerJobExecutor(TaskTypeEnum taskInstanceType, ClientCallbackHandler callbackHandler) {
+ CACHE.put(taskInstanceType, callbackHandler);
+ }
+
+ public static ClientCallbackHandler getClientCallback(Integer type) {
+ return CACHE.get(TaskTypeEnum.valueOf(type));
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackHandler.java
new file mode 100644
index 00000000..7543d4e9
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClientCallbackHandler.java
@@ -0,0 +1,15 @@
+package com.aizuda.easy.retry.server.job.task.handler.callback;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 23:10:50
+ * @since 2.4.0
+ */
+public interface ClientCallbackHandler {
+
+ TaskTypeEnum getTaskInstanceType();
+
+ void callback(ClientCallbackContext context);
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClusterClientCallbackHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClusterClientCallbackHandler.java
new file mode 100644
index 00000000..578fa618
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ClusterClientCallbackHandler.java
@@ -0,0 +1,39 @@
+package com.aizuda.easy.retry.server.job.task.handler.callback;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobExecutorResultDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 23:12:12
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class ClusterClientCallbackHandler extends AbstractClientCallbackHandler {
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.CLUSTER;
+ }
+
+ @Override
+ protected void doCallback(ClientCallbackContext context) {
+
+ JobExecutorResultDTO jobExecutorResultDTO = JobTaskConverter.INSTANCE.toJobExecutorResultDTO(context);
+ jobExecutorResultDTO.setTaskId(context.getTaskId());
+ jobExecutorResultDTO.setMessage(context.getExecuteResult().getMessage());
+ jobExecutorResultDTO.setResult(context.getExecuteResult().getResult());
+ jobExecutorResultDTO.setTaskType(getTaskInstanceType().getType());
+
+ ActorRef actorRef = ActorGenerator.jobTaskExecutorResultActor();
+ actorRef.tell(jobExecutorResultDTO, actorRef);
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ShardingClientCallbackHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ShardingClientCallbackHandler.java
new file mode 100644
index 00000000..ec2082a9
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/callback/ShardingClientCallbackHandler.java
@@ -0,0 +1,40 @@
+package com.aizuda.easy.retry.server.job.task.handler.callback;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobExecutorResultDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-10-07 10:24
+ * @since : 2.4.0
+ */
+@Component
+@Slf4j
+public class ShardingClientCallbackHandler extends AbstractClientCallbackHandler {
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.SHARDING;
+ }
+
+ @Override
+ protected void doCallback(final ClientCallbackContext context) {
+
+ JobExecutorResultDTO jobExecutorResultDTO = JobTaskConverter.INSTANCE.toJobExecutorResultDTO(context);
+ jobExecutorResultDTO.setTaskId(context.getTaskId());
+ jobExecutorResultDTO.setMessage(context.getExecuteResult().getMessage());
+ jobExecutorResultDTO.setResult(context.getExecuteResult().getResult());
+ jobExecutorResultDTO.setTaskType(getTaskInstanceType().getType());
+
+ ActorRef actorRef = ActorGenerator.jobTaskExecutorResultActor();
+ actorRef.tell(jobExecutorResultDTO, actorRef);
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/AbstractJobExecutor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/AbstractJobExecutor.java
new file mode 100644
index 00000000..8764bd31
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/AbstractJobExecutor.java
@@ -0,0 +1,51 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.generator.task.JobTaskGenerateContext;
+import com.aizuda.easy.retry.server.job.task.generator.task.JobTaskGenerator;
+import com.aizuda.easy.retry.server.job.task.generator.task.JobTaskGeneratorFactory;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 22:13:04
+ * @since 2.4.0
+ */
+public abstract class AbstractJobExecutor implements JobExecutor, InitializingBean {
+
+ @Autowired
+ private JobMapper jobMapper;
+
+ @Override
+ public void execute(JobExecutorContext context) {
+
+ // 生成任务
+ JobTaskGenerator taskInstance = JobTaskGeneratorFactory.getTaskInstance(getTaskInstanceType().getType());
+ JobTaskGenerateContext instanceGenerateContext = JobTaskConverter.INSTANCE.toJobTaskInstanceGenerateContext(context);
+ instanceGenerateContext.setTaskBatchId(context.getTaskBatchId());
+ List taskList = taskInstance.generate(instanceGenerateContext);
+ if (CollectionUtils.isEmpty(taskList)) {
+ return;
+ }
+
+ Job job = jobMapper.selectById(context.getJobId());
+ context.setJob(job);
+ context.setTaskList(taskList);
+
+ doExecute(context);
+ }
+
+ protected abstract void doExecute(JobExecutorContext context);
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ JobExecutorFactory.registerJobExecutor(getTaskInstanceType(), this);
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/BroadcastTaskJobExecutor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/BroadcastTaskJobExecutor.java
new file mode 100644
index 00000000..b96ac911
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/BroadcastTaskJobExecutor.java
@@ -0,0 +1,43 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.RealJobExecutorDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-06 10:27:26
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class BroadcastTaskJobExecutor extends AbstractJobExecutor {
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.BROADCAST;
+ }
+
+ @Override
+ protected void doExecute(JobExecutorContext context) {
+
+ Job job = context.getJob();
+ List taskList = context.getTaskList();
+
+ for (JobTask jobTask : taskList) {
+ RealJobExecutorDTO realJobExecutor = JobTaskConverter.INSTANCE.toRealJobExecutorDTO(job, jobTask);
+ ActorRef actorRef = ActorGenerator.jobRealTaskExecutorActor();
+ actorRef.tell(realJobExecutor, actorRef);
+ }
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/ClusterJobExecutor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/ClusterJobExecutor.java
new file mode 100644
index 00000000..bae67fd3
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/ClusterJobExecutor.java
@@ -0,0 +1,42 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.RealJobExecutorDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 22:12:40
+ * @since 2.4.0
+ */
+@Slf4j
+@Component
+public class ClusterJobExecutor extends AbstractJobExecutor {
+
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.CLUSTER;
+ }
+
+ @Override
+ protected void doExecute(JobExecutorContext context) {
+
+ // 调度客户端
+ Job job = context.getJob();
+ List taskList = context.getTaskList();
+ RealJobExecutorDTO realJobExecutor = JobTaskConverter.INSTANCE.toRealJobExecutorDTO(job, taskList.get(0));
+ ActorRef actorRef = ActorGenerator.jobRealTaskExecutorActor();
+ actorRef.tell(realJobExecutor, actorRef);
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutor.java
new file mode 100644
index 00000000..88ace6ea
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutor.java
@@ -0,0 +1,15 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-09-24 11:40:21
+ * @since 2.4.0
+ */
+public interface JobExecutor {
+
+ TaskTypeEnum getTaskInstanceType();
+
+ void execute(JobExecutorContext context);
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutorContext.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutorContext.java
new file mode 100644
index 00000000..14117133
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutorContext.java
@@ -0,0 +1,41 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 22:53:49
+ * @since 2.4.0
+ */
+@Data
+public class JobExecutorContext {
+
+ /**
+ * 组名称
+ */
+ private String groupName;
+
+ /**
+ * 任务id
+ */
+ private Long jobId;
+
+ /**
+ * 任务id
+ */
+ private Long taskBatchId;
+
+ /**
+ * 任务类型
+ */
+ private Integer taskType;
+
+ private List taskList;
+
+ private Job job;
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutorFactory.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutorFactory.java
new file mode 100644
index 00000000..71181ede
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/JobExecutorFactory.java
@@ -0,0 +1,23 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 13:04:09
+ * @since 2.4.0
+ */
+public class JobExecutorFactory {
+
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ public static void registerJobExecutor(TaskTypeEnum taskInstanceType, JobExecutor executor) {
+ CACHE.put(taskInstanceType, executor);
+ }
+
+ public static JobExecutor getJobExecutor(Integer type) {
+ return CACHE.get(TaskTypeEnum.valueOf(type));
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/RealJobExecutorActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/RealJobExecutorActor.java
new file mode 100644
index 00000000..86be9b81
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/RealJobExecutorActor.java
@@ -0,0 +1,142 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import akka.actor.AbstractActor;
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.client.model.request.DispatchJobRequest;
+import com.aizuda.easy.retry.common.core.enums.JobTaskStatusEnum;
+import com.aizuda.easy.retry.common.core.enums.StatusEnum;
+import com.aizuda.easy.retry.common.core.log.LogUtils;
+import com.aizuda.easy.retry.common.core.model.Result;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
+import com.aizuda.easy.retry.server.common.client.RequestBuilder;
+import com.aizuda.easy.retry.server.common.client.RpcClient;
+import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobExecutorResultDTO;
+import com.aizuda.easy.retry.server.job.task.dto.JobLogDTO;
+import com.aizuda.easy.retry.server.job.task.dto.RealJobExecutorDTO;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.github.rholder.retry.Attempt;
+import com.github.rholder.retry.RetryException;
+import com.github.rholder.retry.RetryListener;
+import com.github.rholder.retry.Retryer;
+import com.github.rholder.retry.RetryerBuilder;
+import com.github.rholder.retry.StopStrategies;
+import com.github.rholder.retry.WaitStrategies;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-06 16:42:08
+ * @since 2.4.0
+ */
+@Component(ActorGenerator.REAL_JOB_EXECUTOR_ACTOR)
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+@Slf4j
+public class RealJobExecutorActor extends AbstractActor {
+
+ @Autowired
+ private JobTaskMapper jobTaskMapper;
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder().match(RealJobExecutorDTO.class, realJobExecutorDTO -> {
+ try {
+ doExecute(realJobExecutorDTO);
+ } catch (Exception e) {
+ log.error("请求客户端发生异常", e);
+ }
+ }).build();
+ }
+
+ private void doExecute(RealJobExecutorDTO realJobExecutorDTO) {
+ // 检查客户端是否存在
+ RegisterNodeInfo registerNodeInfo = CacheRegisterTable.getServerNode(realJobExecutorDTO.getGroupName(), realJobExecutorDTO.getClientId());
+ if (Objects.isNull(registerNodeInfo)) {
+ taskExecuteFailure(realJobExecutorDTO, "无可执行的客户端");
+ return;
+ }
+
+ JobLogDTO jobLogDTO = JobTaskConverter.INSTANCE.toJobLogDTO(realJobExecutorDTO);
+ DispatchJobRequest dispatchJobRequest = JobTaskConverter.INSTANCE.toDispatchJobRequest(realJobExecutorDTO);
+
+ // 构建重试组件
+ Retryer> retryer = buildResultRetryer(realJobExecutorDTO);
+
+ try {
+ // 构建请求客户端对象
+ RpcClient rpcClient = buildRpcClient(registerNodeInfo);
+ Result dispatch = retryer.call(() -> rpcClient.dispatch(dispatchJobRequest));
+ if (dispatch.getStatus() == StatusEnum.YES.getStatus() && Objects.equals(dispatch.getData(), Boolean.TRUE)) {
+ jobLogDTO.setMessage("任务调度成功");
+ } else {
+ jobLogDTO.setMessage(dispatch.getMessage());
+ }
+
+ ActorRef actorRef = ActorGenerator.jobLogActor();
+ actorRef.tell(jobLogDTO, actorRef);
+ } catch (Exception e) {
+ log.error("调用客户端失败.", e);
+ Throwable throwable = e;
+ if (e.getClass().isAssignableFrom(RetryException.class)) {
+ RetryException re = (RetryException) e;
+ throwable = re.getLastFailedAttempt().getExceptionCause();
+ taskExecuteFailure(realJobExecutorDTO, throwable.getMessage());
+ }
+ }
+
+ }
+
+ private Retryer> buildResultRetryer(RealJobExecutorDTO realJobExecutorDTO) {
+ Retryer> retryer = RetryerBuilder.>newBuilder()
+ .retryIfException(throwable -> true)
+ .withStopStrategy(StopStrategies.stopAfterAttempt(realJobExecutorDTO.getMaxRetryTimes()))
+ .withWaitStrategy(WaitStrategies.fixedWait(realJobExecutorDTO.getRetryInterval(), TimeUnit.SECONDS))
+ .withRetryListener(new RetryListener() {
+ @Override
+ public void onRetry(Attempt attempt) {
+ if (attempt.hasException()) {
+ LogUtils.error(log, "任务调度失败. taskInstanceId:[{}] count:[{}]",
+ realJobExecutorDTO.getTaskBatchId(), attempt.getAttemptNumber(), attempt.getExceptionCause());
+ JobTask jobTask = new JobTask();
+ jobTask.setRetryCount((int) attempt.getAttemptNumber());
+ jobTaskMapper.updateById(jobTask);
+ }
+ }
+ })
+ .build();
+ return retryer;
+ }
+
+ private RpcClient buildRpcClient(RegisterNodeInfo registerNodeInfo) {
+ RpcClient rpcClient = RequestBuilder.newBuilder()
+ .hostPort(registerNodeInfo.getHostPort())
+ .groupName(registerNodeInfo.getGroupName())
+ .hostId(registerNodeInfo.getHostId())
+ .hostIp(registerNodeInfo.getHostIp())
+ .contextPath(registerNodeInfo.getContextPath())
+ .client(RpcClient.class)
+ .build();
+ return rpcClient;
+ }
+
+ private static void taskExecuteFailure(RealJobExecutorDTO realJobExecutorDTO, String message) {
+ ActorRef actorRef = ActorGenerator.jobTaskExecutorResultActor();
+ JobExecutorResultDTO jobExecutorResultDTO = new JobExecutorResultDTO();
+ jobExecutorResultDTO.setTaskId(realJobExecutorDTO.getTaskBatchId());
+ jobExecutorResultDTO.setJobId(realJobExecutorDTO.getJobId());
+ jobExecutorResultDTO.setTaskBatchId(realJobExecutorDTO.getTaskBatchId());
+ jobExecutorResultDTO.setTaskStatus(JobTaskStatusEnum.FAIL.getStatus());
+ jobExecutorResultDTO.setMessage(message);
+ actorRef.tell(jobExecutorResultDTO, actorRef);
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/ShardingJobExecutor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/ShardingJobExecutor.java
new file mode 100644
index 00000000..52f2d2ca
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/executor/ShardingJobExecutor.java
@@ -0,0 +1,44 @@
+package com.aizuda.easy.retry.server.job.task.handler.executor;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.RealJobExecutorDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+
+ *
+ * @author www.byteblogs.com
+ * @date 2023-10-06 17:33:51
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class ShardingJobExecutor extends AbstractJobExecutor {
+
+ @Override
+ public TaskTypeEnum getTaskInstanceType() {
+ return TaskTypeEnum.SHARDING;
+ }
+
+ @Override
+ protected void doExecute(JobExecutorContext context) {
+ Job job = context.getJob();
+ List taskList = context.getTaskList();
+ for (int i = 0; i < taskList.size(); i++) {
+ RealJobExecutorDTO realJobExecutor = JobTaskConverter.INSTANCE.toRealJobExecutorDTO(job, taskList.get(i));
+ realJobExecutor.setShardingIndex(i);
+ realJobExecutor.setShardingTotal(taskList.size());
+ ActorRef actorRef = ActorGenerator.jobRealTaskExecutorActor();
+ actorRef.tell(realJobExecutor, actorRef);
+ }
+
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/helper/JobTaskBatchHelper.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/helper/JobTaskBatchHelper.java
new file mode 100644
index 00000000..88605a0c
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/helper/JobTaskBatchHelper.java
@@ -0,0 +1,53 @@
+package com.aizuda.easy.retry.server.job.task.handler.helper;
+
+import com.aizuda.easy.retry.common.core.enums.JobTaskBatchStatusEnum;
+import com.aizuda.easy.retry.common.core.enums.JobTaskStatusEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskBatchMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-10-10 16:50
+ */
+@Component
+public class JobTaskBatchHelper {
+
+ @Autowired
+ private JobTaskMapper jobTaskMapper;
+ @Autowired
+ private JobTaskBatchMapper jobTaskBatchMapper;
+
+ public void complete(Long taskBatchId) {
+
+ List jobTasks = jobTaskMapper.selectList(
+ new LambdaQueryWrapper().select(JobTask::getExecuteStatus)
+ .eq(JobTask::getTaskBatchId, taskBatchId));
+
+ if (jobTasks.stream().anyMatch(jobTask -> JobTaskStatusEnum.NOT_COMPLETE.contains(jobTask.getExecuteStatus()))) {
+ return;
+ }
+
+ long failCount = jobTasks.stream().filter(jobTask -> jobTask.getExecuteStatus() == JobTaskBatchStatusEnum.FAIL.getStatus()).count();
+ long stopCount = jobTasks.stream().filter(jobTask -> jobTask.getExecuteStatus() == JobTaskBatchStatusEnum.STOP.getStatus()).count();
+
+ JobTaskBatch jobTaskBatch = new JobTaskBatch();
+ jobTaskBatch.setId(taskBatchId);
+ if (failCount > 0) {
+ jobTaskBatch.setTaskStatus(JobTaskBatchStatusEnum.FAIL.getStatus());
+ } else if (stopCount > 0) {
+ jobTaskBatch.setTaskStatus(JobTaskBatchStatusEnum.STOP.getStatus());
+ } else {
+ jobTaskBatch.setTaskStatus(JobTaskBatchStatusEnum.SUCCESS.getStatus());
+ }
+
+ jobTaskBatchMapper.updateById(jobTaskBatch);
+
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/AbstractJobPrePareHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/AbstractJobPrePareHandler.java
new file mode 100644
index 00000000..bb959c7b
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/AbstractJobPrePareHandler.java
@@ -0,0 +1,19 @@
+package com.aizuda.easy.retry.server.job.task.handler.prepare;
+
+import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 09:57:55
+ * @since 2.4.0
+ */
+public abstract class AbstractJobPrePareHandler implements JobPrePareHandler {
+
+ @Override
+ public void handler(JobTaskPrepareDTO jobPrepareDTO) {
+
+ doHandler(jobPrepareDTO);
+ }
+
+ protected abstract void doHandler(JobTaskPrepareDTO jobPrepareDTO);
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/JobPrePareHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/JobPrePareHandler.java
new file mode 100644
index 00000000..20ee7eee
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/JobPrePareHandler.java
@@ -0,0 +1,15 @@
+package com.aizuda.easy.retry.server.job.task.handler.prepare;
+
+import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 09:34:00
+ * @since 2.4.0
+ */
+public interface JobPrePareHandler {
+
+ boolean matches(Integer status);
+
+ void handler(JobTaskPrepareDTO jobPrepareDTO);
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/RunningJobPrepareHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/RunningJobPrepareHandler.java
new file mode 100644
index 00000000..e1280fd4
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/RunningJobPrepareHandler.java
@@ -0,0 +1,62 @@
+package com.aizuda.easy.retry.server.job.task.handler.prepare;
+
+import com.aizuda.easy.retry.common.core.enums.JobTaskBatchStatusEnum;
+import com.aizuda.easy.retry.server.job.task.BlockStrategy;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
+import com.aizuda.easy.retry.server.job.task.handler.helper.JobTaskBatchHelper;
+import com.aizuda.easy.retry.server.job.task.strategy.BlockStrategies;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskBatchMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+import java.time.ZoneId;
+
+/**
+ * 处理处于{@link JobTaskBatchStatusEnum::RUNNING}状态的任务
+ *
+ * @author www.byteblogs.com
+ * @date 2023-10-05 18:29:22
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class RunningJobPrepareHandler extends AbstractJobPrePareHandler {
+
+ @Autowired
+ private JobTaskBatchHelper jobTaskBatchHelper;
+
+ @Override
+ public boolean matches(Integer status) {
+ return JobTaskBatchStatusEnum.RUNNING.getStatus() == status;
+ }
+
+ @Override
+ protected void doHandler(JobTaskPrepareDTO prepare) {
+ log.info("存在运行中的任务. taskBatchId:[{}]", prepare.getTaskBatchId());
+
+ // 若存在所有的任务都是完成,但是批次上的状态为运行中,则是并发导致的未把批次状态变成为终态,此处做一次兜底处理
+ jobTaskBatchHelper.complete(prepare.getTaskBatchId());
+
+ // 计算超时时间
+ long delay = System.currentTimeMillis() - prepare.getExecutionAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+
+ int blockStrategy = prepare.getBlockStrategy();
+ // 计算超时时间,到达超时时间覆盖任务
+ if (delay > prepare.getExecutorTimeout() * 1000) {
+ log.info("任务执行超时.taskBatchId:[{}] delay:[{}] executorTimeout:[{}]", prepare.getTaskBatchId(), delay, prepare.getExecutorTimeout() * 1000);
+ blockStrategy = BlockStrategies.BlockStrategyEnum.OVERLAY.getBlockStrategy();
+ }
+
+ BlockStrategies.BlockStrategyContext blockStrategyContext = JobTaskConverter.INSTANCE.toBlockStrategyContext(prepare);
+ BlockStrategy blockStrategyInterface = BlockStrategies.BlockStrategyEnum.getBlockStrategy(blockStrategy);
+ blockStrategyInterface.block(blockStrategyContext);
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/TerminalJobPrepareHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/TerminalJobPrepareHandler.java
new file mode 100644
index 00000000..78602c3b
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/TerminalJobPrepareHandler.java
@@ -0,0 +1,42 @@
+package com.aizuda.easy.retry.server.job.task.handler.prepare;
+
+import com.aizuda.easy.retry.common.core.enums.JobTaskBatchStatusEnum;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
+import com.aizuda.easy.retry.server.job.task.generator.batch.JobTaskBatchGenerator;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+/**
+ * 处理处于已完成 {@link JobTaskBatchStatusEnum::COMPLETED} 状态的任务
+ *
+ * @author www.byteblogs.com
+ * @date 2023-10-02 10:16:28
+ * @since 2.4.0
+ */
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@Component
+@Slf4j
+public class TerminalJobPrepareHandler extends AbstractJobPrePareHandler {
+
+ @Autowired
+ private JobTaskBatchGenerator jobTaskBatchGenerator;
+
+ @Override
+ public boolean matches(Integer status) {
+ return Objects.isNull(status);
+ }
+
+ @Override
+ protected void doHandler(JobTaskPrepareDTO jobPrepareDTO) {
+ log.info("无处理中的数据. jobId:[{}]", jobPrepareDTO.getJobId());
+
+ // 生成任务批次
+ jobTaskBatchGenerator.generateJobTaskBatch(JobTaskConverter.INSTANCE.toJobTaskGeneratorContext(jobPrepareDTO));
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/WaitJobPrepareHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/WaitJobPrepareHandler.java
new file mode 100644
index 00000000..4e3d8ff7
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/prepare/WaitJobPrepareHandler.java
@@ -0,0 +1,48 @@
+package com.aizuda.easy.retry.server.job.task.handler.prepare;
+
+import com.aizuda.easy.retry.common.core.enums.JobTaskBatchStatusEnum;
+import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
+import com.aizuda.easy.retry.server.job.task.dto.JobTimerTaskDTO;
+import com.aizuda.easy.retry.server.job.task.handler.timer.JobTimerWheelHandler;
+import com.aizuda.easy.retry.server.job.task.handler.timer.JobTimerTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.time.ZoneId;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 处理处于{@link JobTaskBatchStatusEnum::WAIT}状态的任务
+ *
+ * @author www.byteblogs.com
+ * @date 2023-10-05 18:29:22
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class WaitJobPrepareHandler extends AbstractJobPrePareHandler {
+
+ @Override
+ public boolean matches(Integer status) {
+ return JobTaskBatchStatusEnum.WAITING.getStatus() == status;
+ }
+
+ @Override
+ protected void doHandler(JobTaskPrepareDTO jobPrepareDTO) {
+ log.info("存在待处理任务. taskBatchId:[{}]", jobPrepareDTO.getTaskBatchId());
+
+ // 若时间轮中数据不存在则重新加入
+ if (!JobTimerWheelHandler.isExisted(jobPrepareDTO.getGroupName(), jobPrepareDTO.getJobId())) {
+
+ // 进入时间轮
+ long delay = jobPrepareDTO.getNextTriggerAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
+ - System.currentTimeMillis();
+ JobTimerTaskDTO jobTimerTaskDTO = new JobTimerTaskDTO();
+ jobTimerTaskDTO.setTaskBatchId(jobPrepareDTO.getTaskBatchId());
+
+ JobTimerWheelHandler.register(jobPrepareDTO.getGroupName(), jobPrepareDTO.getJobId(),
+ new JobTimerTask(jobTimerTaskDTO), delay, TimeUnit.MILLISECONDS);
+ }
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/request/ReportDispatchResultPostHttpRequestHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/request/ReportDispatchResultPostHttpRequestHandler.java
new file mode 100644
index 00000000..13bb255a
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/request/ReportDispatchResultPostHttpRequestHandler.java
@@ -0,0 +1,64 @@
+package com.aizuda.easy.retry.server.job.task.handler.request;
+
+import cn.hutool.core.net.url.UrlQuery;
+import com.aizuda.easy.retry.client.model.request.DispatchJobResultRequest;
+import com.aizuda.easy.retry.common.core.enums.StatusEnum;
+import com.aizuda.easy.retry.common.core.log.LogUtils;
+import com.aizuda.easy.retry.common.core.model.EasyRetryRequest;
+import com.aizuda.easy.retry.common.core.model.NettyResult;
+import com.aizuda.easy.retry.common.core.util.JsonUtil;
+import com.aizuda.easy.retry.server.common.handler.PostHttpRequestHandler;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.handler.callback.ClientCallbackContext;
+import com.aizuda.easy.retry.server.job.task.handler.callback.ClientCallbackFactory;
+import com.aizuda.easy.retry.server.job.task.handler.callback.ClientCallbackHandler;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import static com.aizuda.easy.retry.common.core.constant.SystemConstants.HTTP_PATH.REPORT_JOB_DISPATCH_RESULT;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-09-30 23:01:58
+ * @since 2.4.0
+ */
+@Slf4j
+@Component
+public class ReportDispatchResultPostHttpRequestHandler extends PostHttpRequestHandler {
+
+ @Override
+ public boolean supports(String path) {
+ return REPORT_JOB_DISPATCH_RESULT.equals(path);
+ }
+
+ @Override
+ public HttpMethod method() {
+ return HttpMethod.POST;
+ }
+
+ @Override
+ public String doHandler(String content, UrlQuery query, HttpHeaders headers) {
+ LogUtils.info(log, "Client Callback Request. content:[{}]", content);
+
+ EasyRetryRequest retryRequest = JsonUtil.parseObject(content, EasyRetryRequest.class);
+ Object[] args = retryRequest.getArgs();
+
+ DispatchJobResultRequest dispatchJobResultRequest = JsonUtil.parseObject(JsonUtil.toJsonString(args[0]), DispatchJobResultRequest.class);
+
+ ClientCallbackHandler clientCallback = ClientCallbackFactory.getClientCallback(dispatchJobResultRequest.getTaskType());
+
+ ClientCallbackContext context = JobTaskConverter.INSTANCE.toClientCallbackContext(dispatchJobResultRequest);
+ clientCallback.callback(context);
+
+// JobLogDTO jobLogDTO = JobTaskConverter.INSTANCE.toJobLogDTO(dispatchJobResultRequest);
+// ExecuteResult executeResult = dispatchJobResultRequest.getExecuteResult();
+// jobLogDTO.setMessage(executeResult.getMessage());
+// jobLogDTO.setClientId(hostId);
+// ActorRef actorRef = ActorGenerator.jobLogActor();
+// actorRef.tell(jobLogDTO, actorRef);
+
+ return JsonUtil.toJsonString(new NettyResult(StatusEnum.YES.getStatus(), "Report Dispatch Result Processed Successfully", Boolean.TRUE, retryRequest.getReqId()));
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/AbstractJobTaskStopHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/AbstractJobTaskStopHandler.java
new file mode 100644
index 00000000..5861d42e
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/AbstractJobTaskStopHandler.java
@@ -0,0 +1,63 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.common.core.enums.JobTaskStatusEnum;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.JobExecutorResultDTO;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-03 09:48:10
+ * @since 2.4.0
+ */
+public abstract class AbstractJobTaskStopHandler implements JobTaskStopHandler, InitializingBean {
+
+ @Autowired
+ private JobTaskMapper jobTaskMapper;
+
+ @Override
+ public void stop(TaskStopJobContext context) {
+
+ List jobTasks = jobTaskMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(JobTask::getTaskBatchId, context.getTaskBatchId())
+ .eq(JobTask::getExecuteStatus, JobTaskStatusEnum.NOT_COMPLETE)
+ );
+
+ if (CollectionUtils.isEmpty(jobTasks)) {
+ return;
+ }
+
+ context.setJobTasks(jobTasks);
+
+ doStop(context);
+
+ if (context.isNeedUpdateTaskStatus()) {
+ for (final JobTask jobTask : jobTasks) {
+ JobExecutorResultDTO jobExecutorResultDTO = JobTaskConverter.INSTANCE.toJobExecutorResultDTO(jobTask);
+ jobExecutorResultDTO.setTaskStatus(JobTaskStatusEnum.STOP.getStatus());
+ jobExecutorResultDTO.setMessage("任务停止成功");
+ jobExecutorResultDTO.setTaskType(getTaskType().getType());
+ ActorRef actorRef = ActorGenerator.jobTaskExecutorResultActor();
+ actorRef.tell(jobExecutorResultDTO, actorRef);
+ }
+
+ }
+ }
+
+ protected abstract void doStop(TaskStopJobContext context);
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ JobTaskStopFactory.registerTaskStop(getTaskType(), this);
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/BroadcastTaskStopHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/BroadcastTaskStopHandler.java
new file mode 100644
index 00000000..daefe320
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/BroadcastTaskStopHandler.java
@@ -0,0 +1,43 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.handler.ClientNodeAllocateHandler;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.RealStopTaskInstanceDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 12:59:53
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class BroadcastTaskStopHandler extends AbstractJobTaskStopHandler {
+
+ @Override
+ public TaskTypeEnum getTaskType() {
+ return TaskTypeEnum.BROADCAST;
+ }
+
+ @Override
+ public void doStop(TaskStopJobContext context) {
+
+ for (final JobTask jobTask : context.getJobTasks()) {
+ String clientId = jobTask.getClientId();
+ RealStopTaskInstanceDTO taskInstanceDTO = JobTaskConverter.INSTANCE.toRealStopTaskInstanceDTO(context);
+ taskInstanceDTO.setClientId(clientId);
+
+ ActorRef actorRef = ActorGenerator.jobRealStopTaskInstanceActor();
+ actorRef.tell(taskInstanceDTO, actorRef);
+ }
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/ClusterTaskStopHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/ClusterTaskStopHandler.java
new file mode 100644
index 00000000..13a6b356
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/ClusterTaskStopHandler.java
@@ -0,0 +1,51 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import akka.actor.ActorRef;
+import cn.hutool.core.lang.Assert;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.common.handler.ClientNodeAllocateHandler;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.RealStopTaskInstanceDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 12:59:53
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class ClusterTaskStopHandler extends AbstractJobTaskStopHandler {
+
+
+ @Override
+ public TaskTypeEnum getTaskType() {
+ return TaskTypeEnum.CLUSTER;
+ }
+
+ @Override
+ public void doStop(TaskStopJobContext context) {
+ List jobTasks = context.getJobTasks();
+
+ String clientId = jobTasks.get(0).getClientId();
+ RealStopTaskInstanceDTO taskInstanceDTO = JobTaskConverter.INSTANCE.toRealStopTaskInstanceDTO(context);
+ taskInstanceDTO.setClientId(clientId);
+
+ ActorRef actorRef = ActorGenerator.jobRealStopTaskInstanceActor();
+ actorRef.tell(taskInstanceDTO, actorRef);
+
+ }
+
+
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/JobTaskStopFactory.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/JobTaskStopFactory.java
new file mode 100644
index 00000000..eedf410d
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/JobTaskStopFactory.java
@@ -0,0 +1,23 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 13:04:09
+ * @since 2.4.0
+ */
+public class JobTaskStopFactory {
+
+ private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
+
+ public static void registerTaskStop(TaskTypeEnum taskInstanceType, JobTaskStopHandler interrupt) {
+ CACHE.put(taskInstanceType, interrupt);
+ }
+
+ public static JobTaskStopHandler getJobTaskStop(Integer type) {
+ return CACHE.get(TaskTypeEnum.valueOf(type));
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/JobTaskStopHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/JobTaskStopHandler.java
new file mode 100644
index 00000000..0e1e0340
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/JobTaskStopHandler.java
@@ -0,0 +1,16 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 10:43:58
+ * @since 2.4.0
+ */
+public interface JobTaskStopHandler {
+
+ TaskTypeEnum getTaskType();
+
+ void stop(TaskStopJobContext context);
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/RealStopTaskActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/RealStopTaskActor.java
new file mode 100644
index 00000000..8e481801
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/RealStopTaskActor.java
@@ -0,0 +1,66 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import akka.actor.AbstractActor;
+import com.aizuda.easy.retry.client.model.StopJobDTO;
+import com.aizuda.easy.retry.common.core.model.Result;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
+import com.aizuda.easy.retry.server.common.client.RequestBuilder;
+import com.aizuda.easy.retry.server.common.client.RpcClient;
+import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
+import com.aizuda.easy.retry.server.job.task.dto.RealStopTaskInstanceDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-10-07 10:45
+ * @since : 2.4.0
+ */
+@Component(ActorGenerator.REAL_STOP_TASK_INSTANCE_ACTOR)
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+@Slf4j
+public class RealStopTaskActor extends AbstractActor {
+
+ @Override
+ public Receive createReceive() {
+ return receiveBuilder().match(RealStopTaskInstanceDTO.class, realStopTaskInstanceDTO -> {
+ try {
+ doStop(realStopTaskInstanceDTO);
+ } catch (Exception e) {
+ log.error("停止任务执行失败", e);
+ }
+ }).build();
+ }
+
+ private void doStop(final RealStopTaskInstanceDTO realStopTaskInstanceDTO) {
+
+ // 检查客户端是否存在
+ RegisterNodeInfo registerNodeInfo = CacheRegisterTable.getServerNode(realStopTaskInstanceDTO.getGroupName(), realStopTaskInstanceDTO.getClientId());
+ if (Objects.nonNull(registerNodeInfo)) {
+ // 不用关心停止的结果,若服务端尝试终止失败,客户端会兜底进行关闭
+ requestClient(realStopTaskInstanceDTO, registerNodeInfo);
+ }
+ }
+
+ private Result requestClient(RealStopTaskInstanceDTO realStopTaskInstanceDTO, RegisterNodeInfo registerNodeInfo) {
+ RpcClient rpcClient = RequestBuilder.newBuilder()
+ .hostPort(registerNodeInfo.getHostPort())
+ .groupName(realStopTaskInstanceDTO.getGroupName())
+ .hostId(registerNodeInfo.getHostId())
+ .hostIp(registerNodeInfo.getHostIp())
+ .contextPath(registerNodeInfo.getContextPath())
+ .client(RpcClient.class)
+ .build();
+
+ StopJobDTO stopJobDTO = new StopJobDTO();
+ stopJobDTO.setTaskId(realStopTaskInstanceDTO.getTaskBatchId());
+ stopJobDTO.setJobId(realStopTaskInstanceDTO.getJobId());
+ stopJobDTO.setGroupName(realStopTaskInstanceDTO.getGroupName());
+ return rpcClient.stop(stopJobDTO);
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/ShardingTaskStopHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/ShardingTaskStopHandler.java
new file mode 100644
index 00000000..caa50b66
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/ShardingTaskStopHandler.java
@@ -0,0 +1,40 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import akka.actor.ActorRef;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.dto.RealStopTaskInstanceDTO;
+import com.aizuda.easy.retry.server.job.task.enums.TaskTypeEnum;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 12:59:53
+ * @since 2.4.0
+ */
+@Component
+@Slf4j
+public class ShardingTaskStopHandler extends AbstractJobTaskStopHandler {
+
+ @Override
+ public TaskTypeEnum getTaskType() {
+ return TaskTypeEnum.SHARDING;
+ }
+
+ @Override
+ public void doStop(TaskStopJobContext context) {
+
+ for (final JobTask jobTask : context.getJobTasks()) {
+ String clientId = jobTask.getClientId();
+ RealStopTaskInstanceDTO taskInstanceDTO = JobTaskConverter.INSTANCE.toRealStopTaskInstanceDTO(context);
+ taskInstanceDTO.setClientId(clientId);
+
+ ActorRef actorRef = ActorGenerator.jobRealStopTaskInstanceActor();
+ actorRef.tell(taskInstanceDTO, actorRef);
+ }
+
+ }
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/TaskStopJobContext.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/TaskStopJobContext.java
new file mode 100644
index 00000000..49197fe2
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/stop/TaskStopJobContext.java
@@ -0,0 +1,49 @@
+package com.aizuda.easy.retry.server.job.task.handler.stop;
+
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @author www.byteblogs.com
+ * @date 2023-10-02 22:53:49
+ * @since 2.4.0
+ */
+@Data
+public class TaskStopJobContext {
+
+ /**
+ * 组名称
+ */
+ private String groupName;
+
+ /**
+ * 任务id
+ */
+ private Long jobId;
+
+ /**
+ * 任务id
+ */
+ private Long taskBatchId;
+
+ /**
+ * 任务类型
+ */
+ private Integer taskType;
+
+ /**
+ * 下次触发时间
+ */
+ private LocalDateTime nextTriggerAt;
+
+ /**
+ * 是否需要变更任务状态
+ */
+ private boolean needUpdateTaskStatus;
+
+ private List jobTasks;
+
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/timer/JobTimerTask.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/timer/JobTimerTask.java
new file mode 100644
index 00000000..01027034
--- /dev/null
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/timer/JobTimerTask.java
@@ -0,0 +1,80 @@
+package com.aizuda.easy.retry.server.job.task.handler.timer;
+
+import akka.actor.ActorRef;
+import cn.hutool.core.lang.Assert;
+import com.aizuda.easy.retry.common.core.context.SpringContext;
+import com.aizuda.easy.retry.common.core.enums.JobTaskBatchStatusEnum;
+import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
+import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
+import com.aizuda.easy.retry.server.job.task.dto.JobTimerTaskDTO;
+import com.aizuda.easy.retry.server.job.task.dto.TaskExecuteDTO;
+import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskBatchMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.JobTaskBatch;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import io.netty.util.Timeout;
+import io.netty.util.TimerTask;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: www.byteblogs.com
+ * @date : 2023-09-25 17:28
+ * @since 2.4.0
+ */
+@AllArgsConstructor
+@Slf4j
+public class JobTimerTask implements TimerTask {
+
+ private JobTimerTaskDTO jobTimerTaskDTO;
+
+ private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>());
+
+ @Override
+ public void run(final Timeout timeout) throws Exception {
+ // 执行任务调度
+ log.info("开始执行任务调度. 当前时间:[{}] taskId:[{}]", LocalDateTime.now(), jobTimerTaskDTO.getTaskBatchId());
+
+ executor.execute(() -> {
+ Long jobId = 0L;
+ String groupName = "";
+ try {
+ JobTaskBatchMapper jobTaskBatchMapper = SpringContext.getBeanByType(JobTaskBatchMapper.class);
+ JobTaskBatch jobTaskBatch = jobTaskBatchMapper.selectOne(new LambdaQueryWrapper()
+ .select(JobTaskBatch::getJobId, JobTaskBatch::getGroupName, JobTaskBatch::getId)
+ .eq(JobTaskBatch::getId, jobTimerTaskDTO.getTaskBatchId())
+ .eq(JobTaskBatch::getTaskStatus, JobTaskBatchStatusEnum.WAITING.getStatus()));
+ if (Objects.isNull(jobTaskBatch)) {
+ return;
+ }
+
+ jobId = jobTaskBatch.getJobId();
+ groupName = jobTaskBatch.getGroupName();
+ jobTaskBatch.setExecutionAt(LocalDateTime.now());
+ jobTaskBatch.setTaskStatus(JobTaskBatchStatusEnum.RUNNING.getStatus());
+ Assert.isTrue(1 == jobTaskBatchMapper.updateById(jobTaskBatch),
+ () -> new EasyRetryServerException("更新任务失败"));
+
+ TaskExecuteDTO taskExecuteDTO = new TaskExecuteDTO();
+ taskExecuteDTO.setTaskBatchId(jobTimerTaskDTO.getTaskBatchId());
+ taskExecuteDTO.setGroupName(groupName);
+ taskExecuteDTO.setJobId(jobId);
+ ActorRef actorRef = ActorGenerator.jobTaskExecutorActor();
+ actorRef.tell(taskExecuteDTO, actorRef);
+ } catch (Exception e) {
+ log.error("任务调度执行失败", e);
+ } finally {
+ // 清除时间轮的缓存
+ JobTimerWheelHandler.clearCache(groupName, jobId);
+ }
+
+ });
+
+ }
+}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTimerWheelHandler.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/timer/JobTimerWheelHandler.java
similarity index 73%
rename from easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTimerWheelHandler.java
rename to easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/timer/JobTimerWheelHandler.java
index f43d7229..7ebbce90 100644
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTimerWheelHandler.java
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/handler/timer/JobTimerWheelHandler.java
@@ -1,4 +1,4 @@
-package com.aizuda.easy.retry.server.job.task.scan;
+package com.aizuda.easy.retry.server.job.task.handler.timer;
import cn.hutool.core.util.StrUtil;
import com.aizuda.easy.retry.common.core.log.LogUtils;
@@ -34,7 +34,7 @@ public class JobTimerWheelHandler implements Lifecycle {
// tickDuration 和 timeUnit 一格的时间长度
// ticksPerWheel 一圈有多少格
timer = new HashedWheelTimer(
- new CustomizableThreadFactory("jop-task-timer-wheel-"), 100,
+ new CustomizableThreadFactory("job-task-timer-wheel-"), 100,
TimeUnit.MILLISECONDS, 1024);
timer.start();
@@ -45,7 +45,7 @@ public class JobTimerWheelHandler implements Lifecycle {
.build();
}
- public static void register(String groupName, String uniqueId, TimerTask task, long delay, TimeUnit unit) {
+ public static void register(String groupName, Long taskId, TimerTask task, long delay, TimeUnit unit) {
if (delay < 0) {
delay = 0;
@@ -54,36 +54,37 @@ public class JobTimerWheelHandler implements Lifecycle {
// TODO 支持可配置
if (delay > 60 * 1000) {
LogUtils.warn(log, "距离下次执行时间过久, 不满足进入时间轮的条件. groupName:[{}] uniqueId:[{}] delay:[{}ms]",
- groupName, uniqueId, delay);
+ groupName, taskId, delay);
return;
}
- Timeout timeout = getTimeout(groupName, uniqueId);
+ Timeout timeout = getTimeout(groupName, taskId);
if (Objects.isNull(timeout)) {
try {
+ log.info("加入时间轮. delay:[{}ms] taskId:[{}]", delay, taskId);
timeout = timer.newTimeout(task, delay, unit);
- cache.put(getKey(groupName, uniqueId), timeout);
+ cache.put(getKey(groupName, taskId), timeout);
} catch (Exception e) {
LogUtils.error(log, "加入时间轮失败. groupName:[{}] uniqueId:[{}]",
- groupName, uniqueId, e);
+ groupName, taskId, e);
}
}
}
- private static String getKey(String groupName, String uniqueId) {
- return groupName.concat(StrUtil.UNDERLINE).concat(uniqueId);
+ private static String getKey(String groupName, Long uniqueId) {
+ return groupName.concat(StrUtil.UNDERLINE).concat(uniqueId.toString());
}
- public static Timeout getTimeout(String groupName, String uniqueId) {
+ public static Timeout getTimeout(String groupName, Long uniqueId) {
return cache.getIfPresent(getKey(groupName, uniqueId));
}
- public static boolean isExisted(String groupName, String uniqueId) {
+ public static boolean isExisted(String groupName, Long uniqueId) {
return Objects.nonNull(cache.getIfPresent(getKey(groupName, uniqueId)));
}
- public static boolean cancel(String groupName, String uniqueId) {
+ public static boolean cancel(String groupName, Long uniqueId) {
String key = getKey(groupName, uniqueId);
Timeout timeout = cache.getIfPresent(key);
if (Objects.isNull(timeout)) {
@@ -94,7 +95,7 @@ public class JobTimerWheelHandler implements Lifecycle {
return timeout.cancel();
}
- public static void clearCache(String groupName, String uniqueId) {
+ public static void clearCache(String groupName, Long uniqueId) {
cache.invalidate(getKey(groupName, uniqueId));
}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobContext.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobContext.java
deleted file mode 100644
index 8e32793a..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobContext.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.scan;
-
-import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
-import lombok.Data;
-
-/**
- * @author: www.byteblogs.com
- * @date : 2023-09-25 17:35
- */
-@Data
-public class JobContext {
-
- private RegisterNodeInfo registerNodeInfo;
-
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobExecutorActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobExecutorActor.java
deleted file mode 100644
index fd7cdeec..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobExecutorActor.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.scan;
-
-import akka.actor.AbstractActor;
-import akka.actor.ActorRef;
-import com.aizuda.easy.retry.client.model.DispatchJobDTO;
-import com.aizuda.easy.retry.common.core.log.LogUtils;
-import com.aizuda.easy.retry.common.core.model.Result;
-import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
-import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
-import com.aizuda.easy.retry.server.common.client.RequestBuilder;
-import com.aizuda.easy.retry.server.common.client.RpcClient;
-import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
-import com.aizuda.easy.retry.server.job.task.dto.TaskExecuteDTO;
-import com.aizuda.easy.retry.template.datasource.access.AccessTemplate;
-import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
-import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.context.annotation.Scope;
-import org.springframework.stereotype.Component;
-
-/**
- * @author: www.byteblogs.com
- * @date : 2023-09-25 17:41
- */
-@Component(ActorGenerator.JOB_EXECUTOR_ACTOR)
-@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
-@Slf4j
-public class JobExecutorActor extends AbstractActor {
-
- @Autowired
- private JobTaskMapper jobTaskMapper;
-
- @Override
- public Receive createReceive() {
- return receiveBuilder().match(TaskExecuteDTO.class, taskExecute -> {
- try {
- doExecute(taskExecute);
- } catch (Exception e) {
- LogUtils.error(log, "job executor exception. [{}]", taskExecute, e);
- }
- }).build();
- }
-
- private void doExecute(final TaskExecuteDTO taskExecute) {
- // 调度客户端
- JobTask jobTask = jobTaskMapper.selectById(taskExecute.getTaskId());
- RegisterNodeInfo registerNodeInfo = CacheRegisterTable.getServerNode(jobTask.getGroupName(), jobTask.getHostId());
- RpcClient rpcClient = RequestBuilder.newBuilder()
- .hostPort(registerNodeInfo.getHostPort())
- .groupName(registerNodeInfo.getGroupName())
- .hostId(registerNodeInfo.getHostId())
- .hostIp(registerNodeInfo.getHostIp())
- .contextPath(registerNodeInfo.getContextPath())
- .client(RpcClient.class)
- .build();
-
- DispatchJobDTO dispatchJobDTO = new DispatchJobDTO();
- dispatchJobDTO.setJobId(jobTask.getJobId());
- dispatchJobDTO.setTaskId(jobTask.getId());
- dispatchJobDTO.setGroupName(jobTask.getGroupName());
- Result dispatch = rpcClient.dispatch(dispatchJobDTO);
-
- }
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTaskPrepareActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTaskPrepareActor.java
deleted file mode 100644
index 6111f612..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTaskPrepareActor.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.scan;
-
-import akka.actor.AbstractActor;
-import cn.hutool.core.lang.Assert;
-import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
-import com.aizuda.easy.retry.server.common.cache.CacheRegisterTable;
-import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
-import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
-import com.aizuda.easy.retry.server.common.handler.ClientNodeAllocateHandler;
-import com.aizuda.easy.retry.server.job.task.BlockStrategy;
-import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
-import com.aizuda.easy.retry.server.job.task.enums.TaskStatusEnum;
-import com.aizuda.easy.retry.server.job.task.strategy.BlockStrategies;
-import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
-import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
-import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.context.annotation.Scope;
-import org.springframework.stereotype.Component;
-
-import java.time.ZoneId;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 调度任务准备阶段
- *
- * @author www.byteblogs.com
- * @date 2023-09-25 22:20:53
- * @since
- */
-@Component(ActorGenerator.SCAN_JOB_ACTOR)
-@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
-@Slf4j
-public class JobTaskPrepareActor extends AbstractActor {
-
- @Autowired
- private JobMapper jobMapper;
- @Autowired
- private JobTaskMapper jobTaskMapper;
- @Autowired
- protected ClientNodeAllocateHandler clientNodeAllocateHandler;
-
- @Override
- public Receive createReceive() {
- return receiveBuilder().match(JobTaskPrepareDTO.class, job -> {
- try {
- doPrepare(job);
- } catch (Exception e) {
-
- }
- }).build();
- }
-
- private void doPrepare(JobTaskPrepareDTO prepare) {
-
- JobTask jobTask = jobTaskMapper.selectOne(new LambdaQueryWrapper()
- .eq(JobTask::getJobId, prepare.getJobId())
- .in(JobTask::getTaskStatus,
- TaskStatusEnum.WAIT.getStatus(), TaskStatusEnum.INTERRUPTING.getStatus(),
- TaskStatusEnum.INTERRUPTING.getStatus()));
- if (Objects.isNull(jobTask)) {
- // 生成可执行任务
- RegisterNodeInfo serverNode = clientNodeAllocateHandler.getServerNode(prepare.getGroupName());
- if (Objects.isNull(serverNode)) {
- log.error("无可执行的客户端信息. jobId:[{}]", prepare.getJobId());
- return;
- }
-
- jobTask = new JobTask();
- jobTask.setHostId(serverNode.getHostId());
- jobTask.setJobId(prepare.getJobId());
- jobTask.setGroupName(prepare.getGroupName());
- Assert.isTrue(1 == jobTaskMapper.insert(jobTask),
- () -> new EasyRetryServerException("新增调度任务失败.jobId:[{}]", prepare.getJobId()));
-
- // 进入时间轮
- JobContext jobContext = new JobContext();
- jobContext.setRegisterNodeInfo(serverNode);
- long delay = prepare.getNextTriggerAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
- - System.currentTimeMillis();
- JobTimerWheelHandler.register(prepare.getGroupName(), prepare.getJobId().toString(),
- new JobTimerTask(jobTask.getId(), jobTask.getGroupName()), delay, TimeUnit.MILLISECONDS);
- } else {
-
- BlockStrategies.BlockStrategyContext blockStrategyContext = new BlockStrategies.BlockStrategyContext();
- blockStrategyContext.setRegisterNodeInfo(CacheRegisterTable.getServerNode(jobTask.getGroupName(), jobTask.getHostId()));
- BlockStrategy blockStrategy = BlockStrategies.BlockStrategyEnum.getBlockStrategy(
- prepare.getBlockStrategy());
- blockStrategy.block(blockStrategyContext);
- }
-
-
- }
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTimerTask.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTimerTask.java
deleted file mode 100644
index b23da9e9..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/JobTimerTask.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.scan;
-
-import akka.actor.ActorRef;
-import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
-import com.aizuda.easy.retry.server.job.task.dto.TaskExecuteDTO;
-import io.netty.util.Timeout;
-import io.netty.util.TimerTask;
-import lombok.AllArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-import java.time.LocalDateTime;
-
-/**
- * @author: www.byteblogs.com
- * @date : 2023-09-25 17:28
- */
-@AllArgsConstructor
-@Slf4j
-public class JobTimerTask implements TimerTask {
-
- private Long taskId;
- private String groupName;
-
- @Override
- public void run(final Timeout timeout) throws Exception {
- // 执行任务调度
- log.info("开始执行任务调度. 当前时间:[{}]", LocalDateTime.now());
-
- // 先清除时间轮的缓存
- JobTimerWheelHandler.clearCache(groupName, taskId.toString());
-
- TaskExecuteDTO taskExecuteDTO = new TaskExecuteDTO();
- ActorRef actorRef = ActorGenerator.jobTaskExecutorActor();
- actorRef.tell(taskExecuteDTO, actorRef);
- }
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/ScanJobTaskActor.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/ScanJobTaskActor.java
deleted file mode 100644
index 5eef4fb1..00000000
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/scan/ScanJobTaskActor.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package com.aizuda.easy.retry.server.job.task.scan;
-
-import akka.actor.AbstractActor;
-import akka.actor.ActorRef;
-import cn.hutool.core.lang.Assert;
-import com.aizuda.easy.retry.common.core.enums.StatusEnum;
-import com.aizuda.easy.retry.common.core.log.LogUtils;
-import com.aizuda.easy.retry.server.common.akka.ActorGenerator;
-import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
-import com.aizuda.easy.retry.server.common.dto.ScanTask;
-import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
-import com.aizuda.easy.retry.server.common.handler.ClientNodeAllocateHandler;
-import com.aizuda.easy.retry.server.job.task.BlockStrategy;
-import com.aizuda.easy.retry.server.job.task.WaitStrategy;
-import com.aizuda.easy.retry.server.job.task.dto.JobTaskPrepareDTO;
-import com.aizuda.easy.retry.server.job.task.strategy.BlockStrategies;
-import com.aizuda.easy.retry.server.job.task.strategy.BlockStrategies.BlockStrategyContext;
-import com.aizuda.easy.retry.server.job.task.strategy.BlockStrategies.BlockStrategyEnum;
-import com.aizuda.easy.retry.server.job.task.strategy.WaitStrategies;
-import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobMapper;
-import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
-import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
-import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
-import com.aizuda.easy.retry.template.datasource.persistence.po.RetryTask;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
-import io.netty.util.Timeout;
-import io.netty.util.TimerTask;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.context.annotation.Scope;
-import org.springframework.stereotype.Component;
-import org.springframework.util.CollectionUtils;
-
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static com.aizuda.easy.retry.server.job.task.strategy.WaitStrategies.*;
-
-/**
- * JOB任务扫描
- *
- * @author: www.byteblogs.com
- * @date : 2023-09-22 09:08
- * @since 2.4.0
- */
-@Component(ActorGenerator.SCAN_JOB_ACTOR)
-@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
-@Slf4j
-public class ScanJobTaskActor extends AbstractActor {
-
- @Autowired
- private JobMapper jobMapper;
- @Autowired
- private JobTaskMapper jobTaskMapper;
- @Autowired
- protected ClientNodeAllocateHandler clientNodeAllocateHandler;
-
- private static final AtomicLong lastId = new AtomicLong(0L);
-
- @Override
- public Receive createReceive() {
- return receiveBuilder().match(ScanTask.class, config -> {
-
- try {
- doScan(config);
- } catch (Exception e) {
- LogUtils.error(log, "Data scanner processing exception. [{}]", config, e);
- }
-
- }).build();
-
- }
-
- private void doScan(final ScanTask scanTask) {
-
- List jobs = listAvailableJobs(lastId.get());
- if (CollectionUtils.isEmpty(jobs)) {
- // 数据为空则休眠5s
- try {
- Thread.sleep((10 / 2) * 1000);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
-
- // 重置最大id
- lastId.set(0L);
- return;
- }
-
- lastId.set(jobs.get(jobs.size() - 1).getId());
-
- for (Job job : jobs) {
-
- // 更新下次触发时间
- WaitStrategy waitStrategy = WaitStrategyEnum.getWaitStrategy(job.getTriggerType());
- WaitStrategyContext waitStrategyContext = new WaitStrategyContext();
- waitStrategyContext.setTriggerType(job.getTriggerType());
- waitStrategyContext.setTriggerInterval(job.getTriggerInterval());
- waitStrategyContext.setNextTriggerAt(job.getNextTriggerAt());
- job.setNextTriggerAt(waitStrategy.computeRetryTime(waitStrategyContext));
- Assert.isTrue(1 == jobMapper.updateById(job), () -> new EasyRetryServerException("更新job下次触发时间失败.jobId:[{}]", job.getId()));
-
- // 执行预处理阶段
- ActorRef actorRef = ActorGenerator.jobTaskPrepareActor();
- JobTaskPrepareDTO jobTaskPrepareDTO = new JobTaskPrepareDTO();
- jobTaskPrepareDTO.setJobId(job.getId());
- jobTaskPrepareDTO.setTriggerType(job.getTriggerType());
- jobTaskPrepareDTO.setNextTriggerAt(job.getNextTriggerAt());
- actorRef.tell(jobTaskPrepareDTO, actorRef);
- }
-
- }
-
- private List listAvailableJobs(Long lastId) {
- return jobMapper.selectPage(new PageDTO(0, 1000),
- new LambdaQueryWrapper()
- .eq(Job::getJobStatus, StatusEnum.YES.getStatus())
- // TODO 提前10秒把需要执行的任务拉取出来
- .le(Job::getNextTriggerAt, LocalDateTime.now().plusSeconds(10))
- .eq(Job::getDeleted, StatusEnum.NO.getStatus())
- .gt(Job::getId, lastId)
- ).getRecords();
- }
-}
diff --git a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/strategy/BlockStrategies.java b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/strategy/BlockStrategies.java
index 2d28b7bb..32653176 100644
--- a/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/strategy/BlockStrategies.java
+++ b/easy-retry-server/easy-retry-server-job-task/src/main/java/com/aizuda/easy/retry/server/job/task/strategy/BlockStrategies.java
@@ -1,28 +1,19 @@
package com.aizuda.easy.retry.server.job.task.strategy;
-import cn.hutool.core.lang.Assert;
-import com.aizuda.easy.retry.client.model.InterruptJobDTO;
import com.aizuda.easy.retry.common.core.context.SpringContext;
-import com.aizuda.easy.retry.common.core.enums.StatusEnum;
-import com.aizuda.easy.retry.common.core.model.Result;
-import com.aizuda.easy.retry.server.common.client.RequestBuilder;
-import com.aizuda.easy.retry.server.common.client.RpcClient;
-import com.aizuda.easy.retry.server.common.dto.RegisterNodeInfo;
import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
import com.aizuda.easy.retry.server.job.task.BlockStrategy;
-import com.aizuda.easy.retry.server.job.task.enums.TaskStatusEnum;
-import com.aizuda.easy.retry.server.job.task.scan.JobContext;
-import com.aizuda.easy.retry.server.job.task.scan.JobTimerTask;
-import com.aizuda.easy.retry.server.job.task.scan.JobTimerWheelHandler;
-import com.aizuda.easy.retry.template.datasource.persistence.mapper.JobTaskMapper;
-import com.aizuda.easy.retry.template.datasource.persistence.po.Job;
-import com.aizuda.easy.retry.template.datasource.persistence.po.JobTask;
+import com.aizuda.easy.retry.server.job.task.JobTaskConverter;
+import com.aizuda.easy.retry.server.job.task.generator.batch.JobTaskBatchGenerator;
+import com.aizuda.easy.retry.server.job.task.generator.batch.JobTaskBatchGeneratorContext;
+import com.aizuda.easy.retry.server.job.task.handler.stop.JobTaskStopHandler;
+import com.aizuda.easy.retry.server.job.task.handler.stop.JobTaskStopFactory;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import java.util.concurrent.TimeUnit;
+import java.time.LocalDateTime;
/**
* @author: www.byteblogs.com
@@ -62,85 +53,53 @@ public class BlockStrategies {
private String groupName;
- private RegisterNodeInfo registerNodeInfo;
+ /**
+ * 任务类型
+ */
+ private Integer taskType;
+
+ /**
+ * 下次触发时间
+ */
+ private LocalDateTime nextTriggerAt;
+
}
private static final class DiscardBlockStrategy implements BlockStrategy {
@Override
- public boolean block(final BlockStrategyContext context) {
+ public void block(final BlockStrategyContext context) {
log.warn("阻塞策略为丢弃此次执行. jobId:[{}]", context.getJobId());
- return false;
}
}
private static final class OverlayBlockStrategy implements BlockStrategy {
@Override
- public boolean block(final BlockStrategyContext context) {
+ public void block(final BlockStrategyContext context) {
log.warn("阻塞策略为覆盖. jobId:[{}]", context.getJobId());
- // 向客户端发送中断执行指令
- RegisterNodeInfo registerNodeInfo = context.registerNodeInfo;
- RpcClient rpcClient = RequestBuilder.newBuilder()
- .hostPort(registerNodeInfo.getHostPort())
- .groupName(registerNodeInfo.getGroupName())
- .hostId(registerNodeInfo.getHostId())
- .hostIp(registerNodeInfo.getHostIp())
- .contextPath(registerNodeInfo.getContextPath())
- .client(RpcClient.class)
- .build();
- InterruptJobDTO interruptJobDTO = new InterruptJobDTO();
- interruptJobDTO.setTaskId(context.getTaskId());
- interruptJobDTO.setGroupName(context.getGroupName());
- interruptJobDTO.setJobId(context.getJobId());
+ // 停止任务
+ JobTaskStopHandler instanceInterrupt = JobTaskStopFactory.getJobTaskStop(context.taskType);
+ instanceInterrupt.stop(JobTaskConverter.INSTANCE.toStopJobContext(context));
- // TODO 处理结果
- Result result = rpcClient.interrupt(interruptJobDTO);
- Integer taskStatus;
- if (result.getStatus() == StatusEnum.YES.getStatus() && Boolean.TRUE.equals(result.getData())) {
- taskStatus = TaskStatusEnum.INTERRUPT_SUCCESS.getStatus();
-
- // 生成一个新的任务
- JobTask jobTask = new JobTask();
- jobTask.setJobId(context.getJobId());
- jobTask.setGroupName(context.getGroupName());
- JobTaskMapper jobTaskMapper = SpringContext.getBeanByType(JobTaskMapper.class);
- Assert.isTrue(1 == jobTaskMapper.insert(jobTask), () -> new EasyRetryServerException("新增调度任务失败.jobId:[{}]", context.getJobId()));
-
- JobContext jobContext = new JobContext();
- // 进入时间轮
- JobTimerWheelHandler.register(context.getGroupName(), context.getJobId().toString(), new JobTimerTask(jobTask.getJobId(), jobTask.getGroupName()), 1, TimeUnit.MILLISECONDS);
-
- } else {
- taskStatus = TaskStatusEnum.INTERRUPT_FAIL.getStatus();
- }
-
- JobTaskMapper jobTaskMapper = SpringContext.getBeanByType(JobTaskMapper.class);
- JobTask jobTask = new JobTask();
- jobTask.setTaskStatus(taskStatus);
- Assert.isTrue(1 == jobTaskMapper.updateById(jobTask), ()-> new EasyRetryServerException("更新调度任务失败. jopId:[{}]", context.getJobId()));
-
- return true;
+ // 重新生成任务
+ JobTaskBatchGenerator jobTaskBatchGenerator = SpringContext.getBeanByType(JobTaskBatchGenerator.class);
+ JobTaskBatchGeneratorContext jobTaskBatchGeneratorContext = JobTaskConverter.INSTANCE.toJobTaskGeneratorContext(context);
+ jobTaskBatchGenerator.generateJobTaskBatch(jobTaskBatchGeneratorContext);
}
}
private static final class ConcurrencyBlockStrategy implements BlockStrategy {
@Override
- public boolean block(final BlockStrategyContext context) {
+ public void block(final BlockStrategyContext context) {
log.warn("阻塞策略为并行执行. jobId:[{}]", context.getJobId());
- JobTask jobTask = new JobTask();
- jobTask.setJobId(context.getJobId());
- jobTask.setGroupName(context.getGroupName());
- JobTaskMapper jobTaskMapper = SpringContext.getBeanByType(JobTaskMapper.class);
- Assert.isTrue(1 == jobTaskMapper.insert(jobTask), () -> new EasyRetryServerException("新增调度任务失败.jobId:[{}]", context.getJobId()));
-
- // 进入时间轮
- JobTimerWheelHandler.register(context.getGroupName(), context.getJobId().toString(), new JobTimerTask(jobTask.getJobId(), jobTask.getGroupName()), 1, TimeUnit.MILLISECONDS);
-
- return false;
+ // 重新生成任务
+ JobTaskBatchGenerator jobTaskBatchGenerator = SpringContext.getBeanByType(JobTaskBatchGenerator.class);
+ JobTaskBatchGeneratorContext jobTaskBatchGeneratorContext = JobTaskConverter.INSTANCE.toJobTaskGeneratorContext(context);
+ jobTaskBatchGenerator.generateJobTaskBatch(jobTaskBatchGeneratorContext);
}
}
diff --git a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/AbstractSchedule.java b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/AbstractSchedule.java
index 04e6e145..853af4cb 100644
--- a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/AbstractSchedule.java
+++ b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/AbstractSchedule.java
@@ -59,11 +59,11 @@ public abstract class AbstractSchedule implements Schedule {
protected abstract void doExecute();
- abstract String lockName();
+ protected abstract String lockName();
- abstract String lockAtMost();
+ protected abstract String lockAtMost();
- abstract String lockAtLeast();
+ protected abstract String lockAtLeast();
private LockProvider getLockAccess() {
return lockProviders.stream()
diff --git a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/OfflineNodeSchedule.java b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/OfflineNodeSchedule.java
index fdb74995..02b0b9fc 100644
--- a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/OfflineNodeSchedule.java
+++ b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/OfflineNodeSchedule.java
@@ -59,17 +59,17 @@ public class OfflineNodeSchedule extends AbstractSchedule implements Lifecycle {
}
@Override
- String lockName() {
+ public String lockName() {
return "clearOfflineNode";
}
@Override
- String lockAtMost() {
+ public String lockAtMost() {
return "PT10S";
}
@Override
- String lockAtLeast() {
+ public String lockAtLeast() {
return "PT5S";
}
diff --git a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryErrorMoreThresholdAlarmSchedule.java b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryErrorMoreThresholdAlarmSchedule.java
index 829516f4..8a0dbd22 100644
--- a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryErrorMoreThresholdAlarmSchedule.java
+++ b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryErrorMoreThresholdAlarmSchedule.java
@@ -93,17 +93,17 @@ public class RetryErrorMoreThresholdAlarmSchedule extends AbstractSchedule imple
}
@Override
- String lockName() {
+ public String lockName() {
return "retryErrorMoreThreshold";
}
@Override
- String lockAtMost() {
+ public String lockAtMost() {
return "PT10M";
}
@Override
- String lockAtLeast() {
+ public String lockAtLeast() {
return "PT1M";
}
}
diff --git a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskMoreThresholdAlarmSchedule.java b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskMoreThresholdAlarmSchedule.java
index f24dde1f..6fa28355 100644
--- a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskMoreThresholdAlarmSchedule.java
+++ b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskMoreThresholdAlarmSchedule.java
@@ -88,17 +88,17 @@ public class RetryTaskMoreThresholdAlarmSchedule extends AbstractSchedule implem
}
@Override
- String lockName() {
+ public String lockName() {
return "retryTaskMoreThreshold";
}
@Override
- String lockAtMost() {
+ public String lockAtMost() {
return "PT10M";
}
@Override
- String lockAtLeast() {
+ public String lockAtLeast() {
return "PT1M";
}
}
diff --git a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskSchedule.java b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskSchedule.java
index 5d50750e..4cf1b066 100644
--- a/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskSchedule.java
+++ b/easy-retry-server/easy-retry-server-retry-task/src/main/java/com/aizuda/easy/retry/server/retry/task/support/schedule/RetryTaskSchedule.java
@@ -52,17 +52,17 @@ public class RetryTaskSchedule extends AbstractSchedule implements Lifecycle {
}
@Override
- String lockName() {
+ public String lockName() {
return "clearFinishAndMoveDeadLetterRetryTask";
}
@Override
- String lockAtMost() {
+ public String lockAtMost() {
return "PT60s";
}
@Override
- String lockAtLeast() {
+ public String lockAtLeast() {
return "PT60s";
}
}
diff --git a/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/EasyRetryServerApplication.java b/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/EasyRetryServerApplication.java
index 85afa361..69ab7d44 100644
--- a/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/EasyRetryServerApplication.java
+++ b/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/EasyRetryServerApplication.java
@@ -2,12 +2,9 @@ package com.aizuda.easy.retry.server;
import com.aizuda.easy.retry.server.server.NettyHttpServer;
import lombok.extern.slf4j.Slf4j;
-import org.mybatis.spring.annotation.MapperScan;
-import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
diff --git a/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/ConsumerBucketActor.java b/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/ConsumerBucketActor.java
index ae91c30c..011d7943 100644
--- a/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/ConsumerBucketActor.java
+++ b/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/ConsumerBucketActor.java
@@ -13,6 +13,7 @@ import com.aizuda.easy.retry.server.common.dto.ScanTask;
import com.aizuda.easy.retry.server.retry.task.support.cache.CacheGroupRateLimiter;
import com.aizuda.easy.retry.template.datasource.access.AccessTemplate;
import com.aizuda.easy.retry.template.datasource.persistence.mapper.ServerNodeMapper;
+import com.aizuda.easy.retry.template.datasource.persistence.po.GroupConfig;
import com.aizuda.easy.retry.template.datasource.persistence.po.SceneConfig;
import com.aizuda.easy.retry.template.datasource.persistence.po.ServerNode;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -23,8 +24,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -65,27 +68,28 @@ public class ConsumerBucketActor extends AbstractActor {
private void doDispatch(final ConsumerBucket consumerBucket) {
// 查询桶对应组信息
- Set groupNameSet = accessTemplate.getSceneConfigAccess().list(
- new LambdaQueryWrapper().select(SceneConfig::getGroupName)
- .eq(SceneConfig::getSceneStatus, StatusEnum.YES.getStatus())
- .in(SceneConfig::getBucketIndex, consumerBucket.getBuckets())
- .groupBy(SceneConfig::getGroupName)).stream().map(SceneConfig::getGroupName).collect(Collectors.toSet());
+ List groupConfigs = accessTemplate.getGroupConfigAccess().list(
+ new LambdaQueryWrapper()
+ .select(GroupConfig::getGroupName)
+ .eq(GroupConfig::getGroupStatus, StatusEnum.YES.getStatus())
+ .in(GroupConfig::getBucketIndex, consumerBucket.getBuckets())
+ );
- // todo 需要对groupNameSet进行状态过滤只有开启才进行任务调度
- // todo 通过同步线程对集群中的当前节点需要处理的组进行同步
- for (final String groupName : groupNameSet) {
- CacheConsumerGroup.addOrUpdate(groupName);
- ScanTask scanTask = new ScanTask();
- scanTask.setBuckets(consumerBucket.getBuckets());
- scanTask.setGroupName(groupName);
- produceScanActorTask(scanTask);
+ if (!CollectionUtils.isEmpty(groupConfigs)) {
+ for (final GroupConfig groupConfig : groupConfigs) {
+ CacheConsumerGroup.addOrUpdate(groupConfig.getGroupName());
+ ScanTask scanTask = new ScanTask();
+ scanTask.setGroupName(groupConfig.getGroupName());
+ scanTask.setBuckets(consumerBucket.getBuckets());
+ produceScanActorTask(scanTask);
+ }
}
- // job
+ // 扫描回调数据
ScanTask scanTask = new ScanTask();
scanTask.setBuckets(consumerBucket.getBuckets());
- ActorRef jobActor = TaskTypeEnum.JOB.getActorRef().get();
- jobActor.tell(scanTask, jobActor);
+ ActorRef scanJobActorRef = cacheActorRef("DEFAULT_JOB_KEY", TaskTypeEnum.JOB);
+ scanJobActorRef.tell(scanTask, scanJobActorRef);
}
/**
diff --git a/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/DispatchService.java b/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/DispatchService.java
index 0ba4746c..b37bad13 100644
--- a/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/DispatchService.java
+++ b/easy-retry-server/easy-retry-server-starter/src/main/java/com/aizuda/easy/retry/server/dispatch/DispatchService.java
@@ -35,7 +35,7 @@ public class DispatchService implements Lifecycle {
/**
* 调度时长
*/
- public static final Long PERIOD = 10L;
+ public static final Long PERIOD = 60L;
/**
* 延迟10s为了尽可能保障集群节点都启动完成在进行rebalance
diff --git a/pom.xml b/pom.xml
index 2e3ed15a..030e76ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -105,6 +105,11 @@
easy-retry-client-core
${revision}
+
+ com.aizuda
+ easy-retry-client-job-core
+ ${revision}
+
com.aliyun