悠悠楠杉
分布式定时任务那些事儿|青训营笔记,分布式定时任务解决方案
标题:分布式定时任务那些事儿|青训营笔记
关键词:分布式定时任务、调度系统、任务分片、容错机制、青训营
描述:本文深入探讨分布式定时任务的核心技术,包括架构设计、任务分片策略、容错机制实现,并结合实际场景分析典型问题的解决方案。
正文:
一、为什么需要分布式定时任务?
当单机定时任务面临百万级任务调度时,你会遇到两个致命问题:性能瓶颈和单点故障。去年我们电商大促时,就因为单机定时任务崩溃导致优惠券发放延迟,直接损失300万订单——这就是我们转向分布式方案的契机。
二、核心架构设计
2.1 三层调度模型
典型架构包含三个角色(以Apache DolphinScheduler为例):mermaid
graph TD
A[Master节点] -->|任务派发| B[Worker节点]
B -->|心跳上报| C[ZooKeeper]
C -->|选举协调| A
// 伪代码示例:任务派发逻辑
void dispatchTask(Task task) {
if (zk.getActiveMasters() < 3) {
throw new RuntimeException("Master节点不足");
}
Worker worker = loadBalance.selectWorker();
worker.execute(task);
}
关键点:
- Master采用Leader-Follower模式避免脑裂
- Worker通过心跳包上报资源利用率
- 调度决策需考虑跨机房延迟(实测上海-北京机房RTT约35ms)
2.2 任务分片策略
我们曾用一致性哈希分片导致20%任务倾斜,后来改进为动态权重分片:
| 分片算法 | 适用场景 | 缺点 |
|----------|----------|------|
| 轮询分配 | 均匀任务 | 忽略节点负载 |
| 哈希取模 | 固定路由 | 扩容困难 |
| 动态权重 | 弹性伸缩 | 实现复杂 |
// 动态分片示例
List<Shard> shard(Task task, List<Worker> workers) {
return workers.stream()
.sorted(comparing(Worker::getCpuLoad).reversed())
.limit(task.getShardCount())
.map(w -> new Shard(w, task.getDataSlice()))
.collect(toList());
}
三、容错机制实战
3.1 故障转移三原则
- 快速感知:基于gRPC长连接的心跳超时控制在3秒内
- 状态恢复:任务上下文持久化到MySQL+Redis双写
- 避免雪崩:采用阶梯式重试策略(1s/5s/30s)
3.2 幂等性设计
支付系统中遇到的典型案例:
mermaid
sequenceDiagram
定时任务->>+DB: 查询待处理订单
DB-->>-定时任务: 返回100条记录
定时任务->>+MQ: 发送支付消息
MQ-->>-消费者: 网络抖动导致ACK丢失
定时任务->>MQ: 重复发送(灾难!)
解决方案:
boolean isProcessed(String bizId) {
return redis.setnx("lock:"+bizId, "1", 24*3600) == 1;
}
四、踩坑记录
- 时间不同步:曾因NTP服务异常导致跨机房时间差8秒,触发双重执行
- 解决方案:采用TSO(Timestamp Oracle)服务
- 长尾任务:某个ETL任务运行2小时阻塞后续任务
- 改进:增加超时中断和checkpoint机制
五、未来演进方向
- 混合云调度:自建集群+公有云Spot实例混部降低成本
- 智能预测:基于历史数据预测任务耗时,提前资源预热
(完)
