外卖霸王餐库存预热:分布式任务调度Elastic-Job分片与故障转移
业务背景
“吃喝不愁”App每日在特定时段(如午/晚高峰前)需对全国范围内的“霸王餐”活动商品进行库存预热,包括加载商家配额、校验资格、缓存热点数据等。该任务具有以下特点:
- 数据量大(百万级商户);
- 执行时间窗口短(需在10分钟内完成);
- 要求高可用,单点故障不能阻断整体任务。
传统单机定时任务无法满足,因此引入 Apache Elastic-Job 实现分布式分片调度与自动故障转移。
Elastic-Job核心配置
首先引入依赖(以 ElasticJob-Lite 为例):
<dependency>
<groupId>org.apache.shardingsphere.elasticjob</groupId>
<artifactId>elasticjob-lite-core</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere.elasticjob</groupId>
<artifactId>elasticjob-lite-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
ZooKeeper 作为注册中心,用于协调分片与状态同步。

自定义分片任务实现
package juwatech.cn.job;
import org.apache.shardingsphere.elasticjob.api.ShardingContext;
import org.apache.shardingsphere.elasticjob.simple.job.SimpleJob;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class StockPreheatJob implements SimpleJob {
private final StockPreheatService stockPreheatService;
public StockPreheatJob(StockPreheatService stockPreheatService) {
this.stockPreheatService = stockPreheatService;
}
@Override
public void execute(ShardingContext context) {
int shardItem = context.getShardingItem();
int shardTotal = context.getShardingTotalCount();
// 根据分片编号加载对应数据段
List<Long> merchantIds = stockPreheatService.getMerchantIdsByShard(shardItem, shardTotal);
for (Long merchantId : merchantIds) {
try {
stockPreheatService.preheatStock(merchantId);
} catch (Exception e) {
// 记录失败日志,但不中断其他分片
System.err.println("Preheat failed for merchant: " + merchantId + ", error: " + e.getMessage());
}
}
}
}
分片数据划分策略
为保证分片均衡,按商户ID取模或范围切分:
package juwatech.cn.service;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class StockPreheatService {
// 模拟从数据库获取总商户列表(实际应分页或使用游标)
public List<Long> getAllMerchantIds() {
// 假设有 1,000,000 个商户
List<Long> ids = new ArrayList<>();
for (long i = 1L; i <= 1_000_000L; i++) {
ids.add(i);
}
return ids;
}
public List<Long> getMerchantIdsByShard(int shardItem, int totalShards) {
List<Long> all = getAllMerchantIds();
List<Long> result = new ArrayList<>();
for (int i = 0; i < all.size(); i++) {
if (i % totalShards == shardItem) {
result.add(all.get(i));
}
}
return result;
}
public void preheatStock(Long merchantId) {
// 模拟预热逻辑:加载配额、写入Redis、更新状态等
// 实际中应调用DAO或缓存服务
System.out.println("Preheating stock for merchant: " + merchantId);
}
}
Spring Boot 配置
elasticjob:
reg-center:
server-lists: localhost:2181
namespace: eatfree-stock-job
jobs:
stockPreheatJob:
elasticJobClass: juwatech.cn.job.StockPreheatJob
cron: "0 0 10 * * ?" # 每天10点执行
sharding-total-count: 10
sharding-item-parameters: "0=Beijing,1=Shanghai,2=Guangzhou,3=Shenzhen,4=Chengdu,5=Wuhan,6=Xi'an,7=Hangzhou,8=Nanjing,9=Other"
job-parameter: ""
failover: true
misfire: true
max-time-diff-seconds: 60
关键参数说明:
sharding-total-count: 10:将任务分为10个分片;failover: true:启用故障转移,某节点宕机后,其分片由其他存活节点接管;misfire: true:允许错过执行的任务在恢复后补跑。
故障转移机制验证
假设部署3台服务器(A、B、C),共10个分片。初始分配如下:
- A: 分片 0,1,2,3
- B: 分片 4,5,6
- C: 分片 7,8,9
当B宕机,Elastic-Job 通过 ZooKeeper 检测到失联(默认心跳超时30秒),将B的分片(4,5,6)重新分配给A或C继续执行,确保任务不丢失。
监控与日志
可通过 ElasticJob Console 查看分片状态、执行历史、失败记录。同时,在 preheatStock 方法中集成日志追踪:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log = LoggerFactory.getLogger(StockPreheatService.class);
public void preheatStock(Long merchantId) {
log.info("Start preheating stock for merchant: {}", merchantId);
// ... 业务逻辑
log.info("Finish preheating for merchant: {}", merchantId);
}
性能优化建议
- 商户ID列表不应全量加载至内存,应改用数据库分页或基于ID范围查询;
- 每个分片内可并行处理(如使用
parallelStream()),但需控制线程数避免DB压力过大; - Redis 写入采用 Pipeline 批量操作提升吞吐。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!

被折叠的 条评论
为什么被折叠?



