5万字长文
趁还没火赶紧收藏,比知识星球的项目质量高还不付费,你去哪儿找?
基于spring环境开发,拒绝CRUD,让你体验后端的美
从后端角度讲,任务定时调度系统广泛应用于数据备份、日志清理、系统维护等场景,能够显著提高系统的自动化程度和运行效率
mysqldump
、rsync
等。rm
、mv
等。apt-get update
、top
等。在互联网业务中,任务定时调度系统同样扮演着至关重要的角色,它确保了各种自动化流程按时、按需执行,从而维持业务的正常运转和优化用户体验。
数据同步与备份: 定时任务可以用于跨数据库或跨系统的数据同步,确保数据的一致性,以及进行定期的数据备份,防止数据丢失。
缓存更新: 对于一些不经常变动但访问频繁的数据,可以设置定时任务定期更新缓存,提高系统响应速度。
报表生成: 每天、每周或每月生成业务报表,用于分析和决策支持。
定时推送: 如新闻应用的每日新闻摘要推送,或电商平台的促销活动提醒。
会员服务: 自动续费、会员等级调整等都可以通过定时任务实现。
内容更新: 如社交媒体内容的定时发布,保持平台内容的活跃度。
日志清理: 定期清理旧的日志文件,释放存储空间。
性能监控: 定期运行性能检测任务,提前发现并解决潜在问题。
索引重建: 在搜索引擎服务中,定时重建索引以提升搜索效率。
订单处理: 如订单自动取消、自动确认收货等。
支付结算: 定时对账、结算给商家或用户的账户。
库存管理: 定时检查库存,触发自动补货或预警。
活动管理: 自动开启或结束促销活动、限时抢购等。
用户行为分析: 定期分析用户行为数据,为个性化推荐或营销策略提供数据支持。
安全扫描: 定期进行系统的安全检查,防范潜在的安全威胁。
合规检查: 确保业务操作符合法律法规,如数据隐私保护的定期审计。
问就是高可用,可扩展,高容错。
从后端的角度来看,定时任务是指在预定的时间点或时间间隔内自动执行的特定任务。这些任务通常由服务器或分布式系统中的某个组件触发,并由后端服务负责执行。以下是从后端角度详细介绍定时任务的各个方面:
设计一个分布式任务定时调度系统涉及多个层面的考虑,包括任务调度、任务分发、任务执行、状态监控、高可用性、容错机制和扩展性。一个健壮的分布式定时调度系统必须确保在多个节点环境中任务能够按时、高效且可靠地执行。
任务定义与调度配置
调度器(Scheduler)
时间驱动的调度器:通过解析任务的时间表达式(如Cron)确定任务的执行时间。
分布式锁:由于调度器可能有多个实例同时运行,使用分布式锁(如基于Redis、ZooKeeper)保证同一任务不会被多个调度器重复调度。最常见的分布式锁实现包括:
SETNX
命令确保同一时刻只有一个调度器可以调度某个任务。任务分配与负载均衡
任务分发器(Dispatcher) :调度器在调度任务时,需要将任务分发到执行节点。可以基于轮询、随机分配、或任务权重等策略分配任务,常见的负载均衡策略包括:
任务执行器(Executor)
任务状态管理与恢复机制
监控与告警
监控:需要对整个调度系统进行全面的监控,监控内容包括:
告警:任务执行失败、超时、节点故障时触发告警,并支持多种告警方式(如邮件、短信、钉钉,企业微信,飞书等)。
高可用与扩展性设计
+--------------------+
| API Gateway | <--- 用户通过API创建、管理任务
+--------------------+
|
+--------------------+
| Task Scheduler | <--- 任务调度器,负责任务的时间触发、分发
+--------------------+
|
v
+--------------------+ +--------------------+
| Task Dispatcher | ---> | Distributed Lock | <--- 保证任务调度的唯一性(Redis/ZooKeeper)
+--------------------+ +--------------------+
|
v
+---------------------+ +---------------------+ +---------------------+
| Task Executor Node | | Task Executor Node | | Task Executor Node | <--- 执行器,执行任务
+---------------------+ +---------------------+ +---------------------+
|
v
+--------------------+
| Status Tracker | <--- 监控任务执行状态,记录任务日志
+--------------------+
|
v
+--------------------+
| Monitoring/Alert | <--- 监控系统,任务失败或异常时告警
+--------------------+
-- 任务定义表
CREATE TABLE task_definition (
task_id BIGINT PRIMARY KEY,
task_name VARCHAR(100),
task_desc VARCHAR(500),
cron_expression VARCHAR(100),
task_handler VARCHAR(200), -- 任务处理类
task_param TEXT, -- 任务参数(JSON格式)
retry_times INT, -- 重试次数
retry_interval INT, -- 重试间隔(秒)
status TINYINT, -- 任务状态(0:停用 1:启用)
create_time DATETIME,
update_time DATETIME
);
-- 任务执行记录表
CREATE TABLE task_execution (
execution_id BIGINT PRIMARY KEY,
task_id BIGINT,
executor_ip VARCHAR(50), -- 执行器IP
start_time DATETIME, -- 开始时间
end_time DATETIME, -- 结束时间
status TINYINT, -- 执行状态(0:执行中 1:成功 2:失败)
error_msg TEXT, -- 错误信息
create_time DATETIME
);
@Component
public class TaskScheduler {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private TaskExecutorRegistry executorRegistry;
// 使用Redis分布式锁实现任务调度
public void scheduleTask(TaskDefinition task) {
String lockKey = "task_lock:" + task.getTaskId();
try {
// 获取分布式锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (locked) {
// 计算下次执行时间
Date nextFireTime = CronUtils.getNextFireTime(task.getCronExpression());
if (shouldExecuteNow(nextFireTime)) {
// 选择执行器节点
String executorNode = selectExecutor();
// 发送任务到执行器
dispatchTask(task, executorNode);
}
}
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
// 选择执行器节点(一致性哈希实现)
private String selectExecutor() {
List<String> nodes = executorRegistry.getAvailableNodes();
ConsistentHash<String> hash = new ConsistentHash<>(nodes);
return hash.getNode(UUID.randomUUID().toString());
}
}
@Component
public class TaskExecutor {
@Autowired
private TaskExecutionRepository executionRepository;
@Autowired
private ApplicationContext applicationContext;
public void executeTask(TaskDefinition task) {
TaskExecution execution = new TaskExecution();
execution.setTaskId(task.getTaskId());
execution.setExecutorIp(IpUtils.getLocalIp());
execution.setStartTime(new Date());
try {
// 获取任务处理器
TaskHandler handler = (TaskHandler) applicationContext
.getBean(task.getTaskHandler());
// 执行任务
handler.execute(task.getTaskParam());
// 更新执行状态为成功
execution.setStatus(1);
execution.setEndTime(new Date());
} catch (Exception e) {
// 任务执行失败,进行重试
if (shouldRetry(task)) {
retryTask(task);
}
// 更新执行状态为失败
execution.setStatus(2);
execution.setErrorMsg(e.getMessage());
execution.setEndTime(new Date());
}
// 保存执行记录
executionRepository.save(execution);
}
// 任务重试逻辑
private void retryTask(TaskDefinition task) {
// 使用延迟队列实现重试
DelayQueue<RetryTask> delayQueue = new DelayQueue<>();
delayQueue.offer(new RetryTask(task,
task.getRetryInterval(), TimeUnit.SECONDS));
}
}
@Component
public class TaskMonitor {
@Autowired
private AlertService alertService;
// 监控任务执行时长
public void monitorExecutionDuration(TaskExecution execution) {
long duration = execution.getEndTime().getTime() -
execution.getStartTime().getTime();
// 如果执行时间超过阈值,发送告警
if (duration > getThreshold(execution.getTaskId())) {
AlertMessage message = new AlertMessage();
message.setType(AlertType.TASK_TIMEOUT);
message.setContent("Task execution timeout: " + execution.getTaskId());
alertService.sendAlert(message);
}
}
// 监控任务失败
@EventListener(TaskFailedEvent.class)
public void onTaskFailed(TaskFailedEvent event) {
AlertMessage message = new AlertMessage();
message.setType(AlertType.TASK_FAILED);
message.setContent("Task failed: " + event.getTaskId() +
", error: " + event.getErrorMessage());
alertService.sendAlert(message);
}
}
@Component
public class SchedulerHA {
@Autowired
private CuratorFramework zkClient;
private static final String MASTER_PATH = "/scheduler/master";
// 使用ZooKeeper实现主从选举
public void electMaster() {
try {
// 创建临时节点
zkClient.create()
.withMode(CreateMode.EPHEMERAL)
.forPath(MASTER_PATH, IpUtils.getLocalIp().getBytes());
// 成为主节点,开始调度任务
startScheduling();
} catch (Exception e) {
// 创建节点失败,说明已经有主节点
// 注册监听器,等待成为主节点
registerMasterListener();
}
}
// 监听主节点变化
private void registerMasterListener() {
PathChildrenCache cache = new PathChildrenCache(
zkClient, MASTER_PATH, true);
cache.getListenable().addListener((client, event) -> {
if (event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) {
// 主节点下线,重新选举
electMaster();
}
});
}
}
@Component
public class TaskDispatcher {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
// Kafka消息发送回调处理
private class TaskDispatchCallback
implements ListenableFutureCallback<SendResult<String, String>> {
@Override
public void onSuccess(SendResult<String, String> result) {
// 任务发送成功,记录日志
log.info("Task dispatched successfully, offset: {}",
result.getRecordMetadata().offset());
}
@Override
public void onFailure(Throwable ex) {
// 任务发送失败,进行重试
log.error("Failed to dispatch task", ex);
retryDispatch();
}
}
// 任务分发重试机制
private void retryDispatch(TaskDefinition task, String executorNode) {
RetryTemplate retryTemplate = new RetryTemplate();
RetryPolicy retryPolicy = new SimpleRetryPolicy(3); // 最多重试3次
retryTemplate.setRetryPolicy(retryPolicy);
// 指数退避策略
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000L);
backOffPolicy.setMultiplier(2.0);
retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.execute(context -> {
dispatchTask(task, executorNode);
return null;
});
}
}
@Component
public class ExecutorRegistry {
@Autowired
private CuratorFramework zkClient;
private static final String EXECUTOR_PATH = "/task/executors";
// 执行器节点注册
public void registerExecutor() {
String path = EXECUTOR_PATH + "/" + IpUtils.getLocalIp();
try {
// 创建临时节点
zkClient.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(path, "1".getBytes());
// 定期更新节点状态(心跳)
startHeartbeat(path);
} catch (Exception e) {
log.error("Failed to register executor", e);
}
}
// 获取可用执行器列表
public List<String> getAvailableExecutors() {
try {
List<String> children = zkClient.getChildren()
.forPath(EXECUTOR_PATH);
return children.stream()
.map(child -> new String(zkClient.getData()
.forPath(EXECUTOR_PATH + "/" + child)))
.collect(Collectors.toList());
} catch (Exception e) {
log.error("Failed to get available executors", e);
return Collections.emptyList();
}
}
// 心跳更新
private void startHeartbeat(String path) {
ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
try {
// 更新节点数据
zkClient.setData()
.forPath(path, String.valueOf(System.currentTimeMillis())
.getBytes());
} catch (Exception e) {
log.error("Failed to update heartbeat", e);
}
}, 0, 30, TimeUnit.SECONDS);
}
}
@Component
public class TaskTracker {
@Autowired
private TaskExecutionRepository executionRepository;
@Autowired
private TaskMonitor taskMonitor;
// 任务状态更新
public void updateTaskStatus(TaskExecution execution) {
// 保存执行记录
executionRepository.save(execution);
// 发送任务状态变更事件
ApplicationEventPublisher.publishEvent(
new TaskStatusChangeEvent(execution));
// 监控任务执行情况
taskMonitor.monitorExecution(execution);
}
// 任务执行超时检测
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void checkTimeout() {
List<TaskExecution> runningTasks =
executionRepository.findByStatus(TaskStatus.RUNNING);
for (TaskExecution task : runningTasks) {
if (isTimeout(task)) {
// 标记任务超时
task.setStatus(TaskStatus.TIMEOUT);
updateTaskStatus(task);
// 终止任务执行
terminateTask(task);
}
}
}
// 任务执行历史查询
public Page<TaskExecution> queryTaskHistory(
Long taskId, Date startTime, Date endTime, Pageable pageable) {
return executionRepository.findByTaskIdAndTimeRange(
taskId, startTime, endTime, pageable);
}
}
@Component
public class TaskStatistics {
@Autowired
private RedisTemplate redisTemplate;
// 统计任务执行次数
public void incrementExecutionCount(Long taskId) {
String key = "task:stats:exec_count:" + taskId;
redisTemplate.opsForValue().increment(key);
}
// 统计任务执行时长
public void recordExecutionDuration(Long taskId, long duration) {
String key = "task:stats:duration:" + taskId;
redisTemplate.opsForZSet().add(key,
String.valueOf(System.currentTimeMillis()), duration);
}
// 获取任务统计信息
public TaskStats getTaskStats(Long taskId) {
TaskStats stats = new TaskStats();
// 获取执行次数
String countKey =