外卖霸王餐库存预热:分布式任务调度Elastic-Job分片与故障转移

外卖霸王餐库存预热:分布式任务调度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开发者团队,转载请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值