Jobs: refactoring
This commit is contained in:
		
							parent
							
								
									7d0b6bfdec
								
							
						
					
					
						commit
						4fbb6c2e71
					
				@ -18,7 +18,6 @@ package org.thingsboard.server.service.job;
 | 
			
		||||
import jakarta.annotation.PreDestroy;
 | 
			
		||||
import lombok.SneakyThrows;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
			
		||||
@ -51,6 +50,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg;
 | 
			
		||||
import org.thingsboard.server.queue.common.consumer.QueueConsumerManager;
 | 
			
		||||
import org.thingsboard.server.queue.discovery.PartitionService;
 | 
			
		||||
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
 | 
			
		||||
import org.thingsboard.server.queue.settings.TasksQueueConfig;
 | 
			
		||||
import org.thingsboard.server.queue.task.JobStatsService;
 | 
			
		||||
import org.thingsboard.server.queue.util.AfterStartUp;
 | 
			
		||||
import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
			
		||||
@ -74,23 +74,21 @@ public class DefaultJobManager implements JobManager {
 | 
			
		||||
    private final JobStatsService jobStatsService;
 | 
			
		||||
    private final NotificationCenter notificationCenter;
 | 
			
		||||
    private final PartitionService partitionService;
 | 
			
		||||
    private final TasksQueueConfig queueConfig;
 | 
			
		||||
    private final Map<JobType, JobProcessor> jobProcessors;
 | 
			
		||||
    private final Map<JobType, TbQueueProducer<TbProtoQueueMsg<TaskProto>>> taskProducers;
 | 
			
		||||
    private final QueueConsumerManager<TbProtoQueueMsg<JobStatsMsg>> jobStatsConsumer;
 | 
			
		||||
    private final ExecutorService executor;
 | 
			
		||||
    private final ExecutorService consumerExecutor;
 | 
			
		||||
 | 
			
		||||
    @Value("${queue.tasks.partitioning_strategy:tenant}")
 | 
			
		||||
    private String tasksPartitioningStrategy;
 | 
			
		||||
    @Value("${queue.tasks.stats.processing_interval_ms:1000}")
 | 
			
		||||
    private int statsProcessingInterval;
 | 
			
		||||
 | 
			
		||||
    public DefaultJobManager(JobService jobService, JobStatsService jobStatsService, NotificationCenter notificationCenter,
 | 
			
		||||
                             PartitionService partitionService, TbCoreQueueFactory queueFactory, List<JobProcessor> jobProcessors) {
 | 
			
		||||
                             PartitionService partitionService, TbCoreQueueFactory queueFactory, TasksQueueConfig queueConfig,
 | 
			
		||||
                             List<JobProcessor> jobProcessors) {
 | 
			
		||||
        this.jobService = jobService;
 | 
			
		||||
        this.jobStatsService = jobStatsService;
 | 
			
		||||
        this.notificationCenter = notificationCenter;
 | 
			
		||||
        this.partitionService = partitionService;
 | 
			
		||||
        this.queueConfig = queueConfig;
 | 
			
		||||
        this.jobProcessors = jobProcessors.stream().collect(Collectors.toMap(JobProcessor::getType, Function.identity()));
 | 
			
		||||
        this.taskProducers = Arrays.stream(JobType.values()).collect(Collectors.toMap(Function.identity(), queueFactory::createTaskProducer));
 | 
			
		||||
        this.executor = ThingsBoardExecutors.newWorkStealingPool(Math.max(4, Runtime.getRuntime().availableProcessors()), getClass());
 | 
			
		||||
@ -98,7 +96,7 @@ public class DefaultJobManager implements JobManager {
 | 
			
		||||
        this.jobStatsConsumer = QueueConsumerManager.<TbProtoQueueMsg<JobStatsMsg>>builder()
 | 
			
		||||
                .name("job-stats")
 | 
			
		||||
                .msgPackProcessor(this::processStats)
 | 
			
		||||
                .pollInterval(125)
 | 
			
		||||
                .pollInterval(queueConfig.getStatsPollInterval())
 | 
			
		||||
                .consumerCreator(queueFactory::createJobStatsConsumer)
 | 
			
		||||
                .consumerExecutor(consumerExecutor)
 | 
			
		||||
                .build();
 | 
			
		||||
@ -113,7 +111,7 @@ public class DefaultJobManager implements JobManager {
 | 
			
		||||
    @Override
 | 
			
		||||
    public Job submitJob(Job job) {
 | 
			
		||||
        log.debug("Submitting job: {}", job);
 | 
			
		||||
        return jobService.submitJob(job.getTenantId(), job);
 | 
			
		||||
        return jobService.saveJob(job.getTenantId(), job);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@ -196,7 +194,7 @@ public class DefaultJobManager implements JobManager {
 | 
			
		||||
 | 
			
		||||
        job.getConfiguration().setToReprocess(taskFailures);
 | 
			
		||||
 | 
			
		||||
        jobService.submitJob(tenantId, job);
 | 
			
		||||
        jobService.saveJob(tenantId, job);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void submitTask(Task<?> task) {
 | 
			
		||||
@ -207,7 +205,7 @@ public class DefaultJobManager implements JobManager {
 | 
			
		||||
 | 
			
		||||
        TbQueueProducer<TbProtoQueueMsg<TaskProto>> producer = taskProducers.get(task.getJobType());
 | 
			
		||||
        EntityId entityId = null;
 | 
			
		||||
        if (tasksPartitioningStrategy.equals("entity")) {
 | 
			
		||||
        if (queueConfig.getPartitioningStrategy().equals("entity")) {
 | 
			
		||||
            entityId = task.getEntityId();
 | 
			
		||||
        }
 | 
			
		||||
        if (entityId == null) {
 | 
			
		||||
@ -257,7 +255,7 @@ public class DefaultJobManager implements JobManager {
 | 
			
		||||
        });
 | 
			
		||||
        consumer.commit();
 | 
			
		||||
 | 
			
		||||
        Thread.sleep(statsProcessingInterval);
 | 
			
		||||
        Thread.sleep(queueConfig.getStatsProcessingInterval());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void sendJobFinishedNotification(Job job) {
 | 
			
		||||
 | 
			
		||||
@ -1908,9 +1908,12 @@ queue:
 | 
			
		||||
    # In a single-tenant environment, use 'entity' strategy to distribute the tasks among multiple partitions.
 | 
			
		||||
    partitioning_strategy: "${TB_QUEUE_TASKS_PARTITIONING_STRATEGY:tenant}"
 | 
			
		||||
    stats:
 | 
			
		||||
      # Name for the tasks stats topic
 | 
			
		||||
      topic: "${TB_QUEUE_TASKS_STATS_TOPIC:jobs.stats}"
 | 
			
		||||
      # Poll interval in milliseconds for tasks stats topic
 | 
			
		||||
      poll_interval: "${TB_QUEUE_TASKS_STATS_POLL_INTERVAL_MS:500}"
 | 
			
		||||
      # Interval in milliseconds to process job stats
 | 
			
		||||
      processing_interval_ms: "${TB_QUEUE_TASKS_STATS_PROCESSING_INTERVAL_MS:1000}"
 | 
			
		||||
      processing_interval: "${TB_QUEUE_TASKS_STATS_PROCESSING_INTERVAL_MS:1000}"
 | 
			
		||||
 | 
			
		||||
# Event configuration parameters
 | 
			
		||||
event:
 | 
			
		||||
 | 
			
		||||
@ -52,7 +52,7 @@ import static org.mockito.Mockito.verify;
 | 
			
		||||
 | 
			
		||||
@DaoSqlTest
 | 
			
		||||
@TestPropertySource(properties = {
 | 
			
		||||
        "queue.tasks.stats.processing_interval_ms=0"
 | 
			
		||||
        "queue.tasks.stats.processing_interval=0"
 | 
			
		||||
})
 | 
			
		||||
public class JobManagerTest extends AbstractControllerTest {
 | 
			
		||||
 | 
			
		||||
@ -203,7 +203,7 @@ public class JobManagerTest extends AbstractControllerTest {
 | 
			
		||||
                .description("test job")
 | 
			
		||||
                .configuration(DummyJobConfiguration.builder()
 | 
			
		||||
                        .successfulTasksCount(tasksCount)
 | 
			
		||||
                        .taskProcessingTimeMs(100)
 | 
			
		||||
                        .taskProcessingTimeMs(500)
 | 
			
		||||
                        .build())
 | 
			
		||||
                .build()).getId();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
 | 
			
		||||
 | 
			
		||||
@DaoSqlTest
 | 
			
		||||
@TestPropertySource(properties = {
 | 
			
		||||
        "queue.tasks.stats.processing_interval_ms=0",
 | 
			
		||||
        "queue.tasks.stats.processing_interval=0",
 | 
			
		||||
        "queue.tasks.partitioning_strategy=entity",
 | 
			
		||||
        "queue.tasks.partitions_per_type=DUMMY:100;DUMMY:50"
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ import org.thingsboard.server.dao.entity.EntityDaoService;
 | 
			
		||||
 | 
			
		||||
public interface JobService extends EntityDaoService {
 | 
			
		||||
 | 
			
		||||
    Job submitJob(TenantId tenantId, Job job);
 | 
			
		||||
    Job saveJob(TenantId tenantId, Job job);
 | 
			
		||||
 | 
			
		||||
    Job findJobById(TenantId tenantId, JobId jobId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.common.data.job;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.Valid;
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
import jakarta.validation.constraints.NotNull;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
@ -43,6 +44,7 @@ public class Job extends BaseData<JobId> implements HasTenantId {
 | 
			
		||||
    private String description;
 | 
			
		||||
    private JobStatus status;
 | 
			
		||||
    @NotNull
 | 
			
		||||
    @Valid
 | 
			
		||||
    private JobConfiguration configuration;
 | 
			
		||||
    private JobResult result;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,8 @@ public abstract class JobResult implements Serializable {
 | 
			
		||||
    private List<TaskResult> results = new ArrayList<>();
 | 
			
		||||
    private String generalError;
 | 
			
		||||
 | 
			
		||||
    private long startTs;
 | 
			
		||||
    private long finishTs;
 | 
			
		||||
    private long cancellationTs;
 | 
			
		||||
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
 | 
			
		||||
@ -25,8 +25,10 @@ import java.util.List;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class JobStats {
 | 
			
		||||
 | 
			
		||||
    private final TenantId tenantId;
 | 
			
		||||
    private final JobId jobId;
 | 
			
		||||
    private final List<TaskResult> taskResults = new ArrayList<>();
 | 
			
		||||
    private Integer totalTasksCount;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -536,10 +536,6 @@ message ToEdqsCoreServiceMsg {
 | 
			
		||||
  bytes value = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ToJobManagerMsg {
 | 
			
		||||
  bytes value = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message LwM2MRegistrationRequestMsg {
 | 
			
		||||
  string tenantId = 1;
 | 
			
		||||
  string endpoint = 2;
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,11 @@ import org.thingsboard.server.queue.TbQueueMsg;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.concurrent.TimeoutException;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@ -39,6 +43,7 @@ public class QueueConsumerManager<M extends TbQueueMsg> {
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    private final TbQueueConsumer<M> consumer;
 | 
			
		||||
    private Future<?> consumerTask;
 | 
			
		||||
    private volatile boolean stopped;
 | 
			
		||||
 | 
			
		||||
    @Builder
 | 
			
		||||
@ -63,7 +68,7 @@ public class QueueConsumerManager<M extends TbQueueMsg> {
 | 
			
		||||
 | 
			
		||||
    public void launch() {
 | 
			
		||||
        log.info("[{}] Launching consumer", name);
 | 
			
		||||
        consumerExecutor.submit(() -> {
 | 
			
		||||
        consumerTask = consumerExecutor.submit(() -> {
 | 
			
		||||
            if (threadPrefix != null) {
 | 
			
		||||
                ThingsBoardThreadFactory.addThreadNamePrefix(threadPrefix);
 | 
			
		||||
            }
 | 
			
		||||
@ -101,6 +106,13 @@ public class QueueConsumerManager<M extends TbQueueMsg> {
 | 
			
		||||
        log.debug("[{}] Stopping consumer", name);
 | 
			
		||||
        stopped = true;
 | 
			
		||||
        consumer.unsubscribe();
 | 
			
		||||
        try {
 | 
			
		||||
            if (consumerTask != null) {
 | 
			
		||||
                consumerTask.get(10, TimeUnit.SECONDS);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
 | 
			
		||||
            log.error("[{}] Failed to await consumer loop stop", name, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface MsgPackProcessor<M extends TbQueueMsg> {
 | 
			
		||||
 | 
			
		||||
@ -15,18 +15,27 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.queue.settings;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@Getter
 | 
			
		||||
@Component
 | 
			
		||||
public class TasksQueueConfig {
 | 
			
		||||
 | 
			
		||||
    @Value("${queue.tasks.poll_interval}")
 | 
			
		||||
    @Value("${queue.tasks.poll_interval:500}")
 | 
			
		||||
    private int pollInterval;
 | 
			
		||||
 | 
			
		||||
    @Value("${queue.tasks.stats.topic}")
 | 
			
		||||
    @Value("${queue.tasks.partitioning_strategy:tenant}")
 | 
			
		||||
    private String partitioningStrategy;
 | 
			
		||||
 | 
			
		||||
    @Value("${queue.tasks.stats.topic:jobs.stats}")
 | 
			
		||||
    private String statsTopic;
 | 
			
		||||
 | 
			
		||||
    @Value("${queue.tasks.stats.poll_interval:500}")
 | 
			
		||||
    private int statsPollInterval;
 | 
			
		||||
 | 
			
		||||
    @Value("${queue.tasks.stats.processing_interval:1000}")
 | 
			
		||||
    private int statsProcessingInterval;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -53,12 +53,13 @@ public class JobStatsService {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void report(TenantId tenantId, JobId jobId, JobStatsMsg.Builder statsMsg) {
 | 
			
		||||
        log.debug("[{}] Reporting: {}", jobId, statsMsg);
 | 
			
		||||
        log.debug("[{}][{}] Reporting: {}", tenantId, jobId, statsMsg);
 | 
			
		||||
        statsMsg.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
 | 
			
		||||
                .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
 | 
			
		||||
                .setJobIdMSB(jobId.getId().getMostSignificantBits())
 | 
			
		||||
                .setJobIdLSB(jobId.getId().getLeastSignificantBits());
 | 
			
		||||
 | 
			
		||||
        // using job id as msg key so that all stats for a certain job are submitted to the same partition
 | 
			
		||||
        TbProtoQueueMsg<JobStatsMsg> msg = new TbProtoQueueMsg<>(jobId.getId(), statsMsg.build());
 | 
			
		||||
        producer.send(TopicPartitionInfo.builder().topic(producer.getDefaultTopic()).build(), msg, TbQueueCallback.EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ public class DefaultJobService extends AbstractEntityService implements JobServi
 | 
			
		||||
 | 
			
		||||
    @Transactional
 | 
			
		||||
    @Override
 | 
			
		||||
    public Job submitJob(TenantId tenantId, Job job) {
 | 
			
		||||
    public Job saveJob(TenantId tenantId, Job job) {
 | 
			
		||||
        if (jobDao.existsByTenantAndKeyAndStatusOneOf(tenantId, job.getKey(), QUEUED, PENDING, RUNNING)) {
 | 
			
		||||
            throw new IllegalArgumentException("The same job is already queued or running");
 | 
			
		||||
        }
 | 
			
		||||
@ -62,6 +62,7 @@ public class DefaultJobService extends AbstractEntityService implements JobServi
 | 
			
		||||
            job.setStatus(QUEUED);
 | 
			
		||||
        } else {
 | 
			
		||||
            job.setStatus(PENDING);
 | 
			
		||||
            job.getResult().setStartTs(System.currentTimeMillis());
 | 
			
		||||
        }
 | 
			
		||||
        return saveJob(tenantId, job, true, null);
 | 
			
		||||
    }
 | 
			
		||||
@ -140,6 +141,7 @@ public class DefaultJobService extends AbstractEntityService implements JobServi
 | 
			
		||||
                    job.setStatus(COMPLETED);
 | 
			
		||||
                    publishEvent = true;
 | 
			
		||||
                }
 | 
			
		||||
                result.setFinishTs(System.currentTimeMillis());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user