PowerJob大数据量任务模拟测试以及部分源码分析

1,098 阅读2分钟

毫无疑问,目前市面上最流行的开源分布式任务调度中间件非PowerJob莫属。今天也是模拟测试分析下Powerjob的性能

1.数据准备

参考powerjob官方给的详细文档搭建powerjob-server和powerjob-worker

server和worker 服务器配置 4C8G服务 模拟了1W条每五分钟执行一次的CORN任务0 0/5 * * * ?

INSERT INTO job_info (id, alarm_config, app_id, concurrency, designated_workers, dispatch_strategy, execute_type, extra, gmt_create, gmt_modified, instance_retry_num, instance_time_limit, job_description, job_name, job_params, lifecycle, log_config, max_instance_num, max_worker_count, min_cpu_cores, min_disk_space, min_memory_space, next_trigger_time, notify_user_ids, processor_info, processor_type, status, tag, task_retry_num, time_expression, time_expression_type) VALUES (1, '', 1, 5, '', 1, 1, NULL, '2023-02-06 19:33:07.598000', '2023-06-26 10:29:48.465000', 0, 0, '任务1', '任务1', 'Hello World 1', '{\"end\":1687881600000,\"start\":1675180800000}', '{\"level\":2,\"type\":1}', 1, 0, 0, 0, 0, 1675699200000, NULL, 'tech.powerjob.samples.processors.SimpleProcessor', 1, 1, NULL, 1, '0 0/5 * * * ? *', 2);

因powerjob的分组隔离设计,一组worker集群对应一个server,故server部署一个实例,worker部署四个实例,此时发现server主扫库线程tech.powerjob.server.core.scheduler.PowerScheduleService#scheduleCronJobCore 执行耗时严重 图片.png powerjob定时扫库执行任务的源码如下

private void scheduleCronJobCore(List<Long> appIds) {

    long nowTime = System.currentTimeMillis();
    long timeThreshold = nowTime + 2 * SCHEDULE_RATE;
    Lists.partition(appIds, MAX_APP_NUM).forEach(partAppIds -> {

        try {

            // 查询条件:任务开启 + 使用CRON表达调度时间 + 指定appId + 即将需要调度执行
            List<JobInfoDO> jobInfos = jobInfoRepository.findByAppIdInAndStatusAndTimeExpressionTypeAndNextTriggerTimeLessThanEqual(partAppIds, SwitchableStatus.ENABLE.getV(), TimeExpressionType.CRON.getV(), timeThreshold);

            if (CollectionUtils.isEmpty(jobInfos)) {
                return;
            }

            // 1. 批量写日志表
            Map<Long, Long> jobId2InstanceId = Maps.newHashMap();
            log.info("[CronScheduler] These cron jobs will be scheduled: {}.", jobInfos);

            jobInfos.forEach(jobInfo -> {
                Long instanceId = instanceService.create(jobInfo.getId(), jobInfo.getAppId(), jobInfo.getJobParams(), null, null, jobInfo.getNextTriggerTime()).getInstanceId();
                jobId2InstanceId.put(jobInfo.getId(), instanceId);
            });
            instanceInfoRepository.flush();

            // 2. 推入时间轮中等待调度执行
            jobInfos.forEach(jobInfoDO -> {

                Long instanceId = jobId2InstanceId.get(jobInfoDO.getId());

                long targetTriggerTime = jobInfoDO.getNextTriggerTime();
                long delay = 0;
                if (targetTriggerTime < nowTime) {
                    log.warn("[Job-{}] schedule delay, expect: {}, current: {}", jobInfoDO.getId(), targetTriggerTime, System.currentTimeMillis());
                } else {
                    delay = targetTriggerTime - nowTime;
                }

                InstanceTimeWheelService.schedule(instanceId, delay, () -> dispatchService.dispatch(jobInfoDO, instanceId, Optional.empty(), Optional.empty()));
            });

            // 3. 计算下一次调度时间(忽略5S内的重复执行,即CRON模式下最小的连续执行间隔为 SCHEDULE_RATE ms)
            jobInfos.forEach(jobInfoDO -> {
                try {
                    refreshJob(jobInfoDO);
                } catch (Exception e) {
                    log.error("[Job-{}] refresh job failed.", jobInfoDO.getId(), e);
                }
            });
            jobInfoRepository.flush();


        } catch (Exception e) {
            log.error("[CronScheduler] schedule cron job failed.", e);
        }
    });
}

耗时主要集中在查库即将要执行的任务以及日志写库,最终得出结论,powerjob采用分组隔离,一个appName等于一个业务集群,也就是实际的一个Java 项目。无限横向扩容的策略适用于接入的业务集群有多组的情况下,单业务集群任务量较少的应用场景,因为一个业务集群多个实例对应一个server。如果接入的场景换为类似PasS和SaaS平台,此时有一个业务集群一个worker包含四个实例,同时1w条任务触发从server端请求同一组worker,此时server查库耗时严重几乎无法正常调度。

举例如下: 假设有10000个调度任务 分为两种情况

一.1000组worker集群*10个任务

二.1组worker集群*10000个任务

PowerJob在情况一场景下性能很棒,server可以无限扩容, 但是场景二情况下单个server扫库因数据量较大调度压力过大,几乎无法正常调度