目录

🥝前言:

🥝实现逻辑:

🥝创建重试表

🥝消息队列进行异步消费

🥝Elastic-Job进行定时任务调度

🥝Elastic-Job设置

🥝 return new SpringJobScheduler()

🥝定义一个JobExceptionHandler,用来处理任务执行过程中的异常。在处理异常时需要根据异常类型来判断是否需要重试。

🥝重试机制实现

🥝在Elastic-Job配置中,可以通过设置JobProperties来实现重试机制的相关配置。常用的设置包括:

🥝Elastic-Job实现流程:


🥝前言:

     你知道的,笔记!

     在面试的时候,可能都会涉及到实战业务场景问题, 今天分享一个,希望面试能够用得上,提供具体实现思路...

🥝实现逻辑:

下单消息失败,消费者在处理消息时,先判断该订单号在重试的表有没有数据,如果有则直接把当前消息保存重试表,如果没有则进行业务处理,如果出现异常,把该消息保存到重试表,用elastic-job失败重试机制,当然还可以使用其他业务来完成,或者海豚调度也是可以的我记得



🥝创建重试表

id:消息唯一标识
orderNo:订单号
message:消息内容
retryCount:重试次数
nextRetryTime:下次重试时间
createTime:创建时间
updateTime:更新时间


CREATE TABLE retry_order (
  id varchar(32) NOT NULL,
  order_no varchar(32) NOT NULL,
  message varchar(2048) NOT NULL,
  retry_count int NOT NULL,
  next_retry_time datetime NOT NULL,
  create_time datetime NOT NULL,
  update_time datetime NOT NULL,
  PRIMARY KEY (id)
);

🥝消息队列进行异步消费

消息队列中注册一个消息监听器,然后根据推送的消息去实现业务逻辑处理

@Service
public class OrderMessageConsumer implements MessageListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderMessageConsumer.class);

    @Autowired
    private OrderService orderService;

    @Autowired
    private RetryOrderService retryOrderService;

    @Override
    public Action consume(Message message, ConsumeContext context) {
        try {
            // 解析消息内容
            String orderNo = new String(message.getBody(), Charset.forName("UTF-8"));

            // 判断重试表中是否存在该订单号的记录
            RetryOrder retryOrder = retryOrderService.getByOrderNo(orderNo);
            if (retryOrder != null) {
                // 保存消息到重试表中
                retryOrder.setMessage(message.getBody());
                retryOrder.setNextRetryTime(new Date());
                retryOrderService.save(retryOrder);
                return Action.CommitMessage;
            }

            // 处理业务逻辑
            orderService.createOrder(orderNo);

            return Action.CommitMessage;
        } catch (Exception e) {
            LOGGER.error("Consume message failed", e);
            // 保存消息到重试表中
            RetryOrder retryOrder = new RetryOrder();
            retryOrder.setId(UUID.randomUUID().toString());
            retryOrder.setOrderNo(new String(message.getBody(), Charset.forName("UTF-8")));
            retryOrder.setMessage(message.getBody());
            retryOrder.setRetryCount(0);
            retryOrder.setNextRetryTime(new Date());
            retryOrderService.save(retryOrder);
            return Action.ReconsumeLater;
        }
    }

}

 

优化前的代码中使用了消息队列进行异步消费,因此需要实现MessageListener接口来处理消息。这种方式下,消息队列会自动将消息推送给MessageListener的实现类进行处理,不需要手动触发任务。

优化后的代码中,使用了Elastic-Job进行定时任务调度。由于Elastic-Job提供了任务分片机制,可以将任务分成多个片段,并通过多个作业节点并发执行,因此可以替代消息队列的异步消费方式。

在使用Elastic-Job进行任务调度时,我们一般会使用SimpleJob来处理任务。SimpleJob是Elastic-Job提供的一个接口,只有一个execute方法,用来处理任务业务逻辑。Elastic-Job会自动将任务分片成多个片段,并调用execute方法进行处理。

🥝Elastic-Job进行定时任务调度

@Slf4j
@Service
@ElasticJobConf(name = "orderJob", jobType = JobType.SIMPLE)
public class OrderMessageConsumer implements SimpleJob {

    @Autowired
    private OrderService orderService;

    @Autowired
    private RetryOrderService retryOrderService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @ShardingItem(key = "orderNo")
    public void execute(ShardingContext context) {
        try {
            // 解析消息内容
            String orderNo = new String(context.getShardingParameter(), Charset.forName("UTF-8"));

            // 判断重试表中是否存在该订单号的记录
            RetryOrder retryOrder = retryOrderService.getByOrderNo(orderNo);
            if (retryOrder != null) {
                // 保存消息到重试表中
                retryOrder.setMessage(context.getJobName().getBytes());
                retryOrder.setNextRetryTime(new Date());
                retryOrderService.save(retryOrder);
                return;
            }

            // 处理业务逻辑
            orderService.createOrder(orderNo);
        } catch (Exception e) {
            log.error("Consume message failed", e);
            // 保存消息到重试表中
            RetryOrder retryOrder = new RetryOrder();
            retryOrder.setId(UUID.randomUUID().toString());
            retryOrder.setOrderNo(new String(context.getShardingParameter(), Charset.forName("UTF-8")));
            retryOrder.setMessage(context.getJobName().getBytes());
            retryOrder.setRetryCount(0);
            retryOrder.setNextRetryTime(new Date());
            retryOrderService.save(retryOrder);
            throw new RuntimeException(e);
        }
    }

}

🥝Elastic-Job设置

@Configuration
public class ElasticJobConfiguration {

    @Autowired
    private ZookeeperRegistryCenter regCenter;

    @Autowired
    private JobEventConfiguration elasticJobListener;

    @Autowired
    private OrderMessageConsumer orderMessageConsumer;

    @Bean(initMethod = "init")
    public JobScheduler orderJobScheduler() {
        String cron = "0/10 * * * * ?";
        int shardingTotalCount = 3;
        String shardingItemParameters = "0=a,1=b,2=c";
        String jobParameter = "orderJob";
        String description = "订单消息处理任务";

        JobProperties jobProperties = new JobProperties();
        jobProperties.setProperty("job_exception_handler", CustomJobExceptionHandler.class.getCanonicalName());
        jobProperties.setProperty("max_time_diff_seconds", "600");
        jobProperties.setProperty("misfire", "false");
        jobProperties.setProperty("failover", "true");

        JobConfig jobConfig = new JobCoreConfiguration("orderJob", cron, shardingTotalCount)
                .jobProperties(jobProperties)
                .shardingItemParameters(shardingItemParameters)
                .jobParameter(jobParameter)
                .description(description);

        LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder(jobConfig)
                .overwrite(true)
                .disabled(false)
                .monitorExecution(true)
                .maxTimeDiffSeconds(10)
                .jobShardingStrategyClass(AverageAllocationJobShardingStrategy.class.getCanonicalName())
                .reconcileIntervalMinutes(30)
                .build();

        return new SpringJobScheduler(orderMessageConsumer, regCenter, liteJobConfiguration, elasticJobListener);
    }

}
🥝 return new SpringJobScheduler()

这段代码用来创建一个SpringJobScheduler对象,用于启动Elastic-Job分布式任务调度框架。

在创建SpringJobScheduler对象时,需要传入以下参数:

  • orderMessageConsumer:实现了Elastic-Job的SimpleJob接口的任务处理类。
  • regCenter:注册中心实例。
  • liteJobConfiguration:作业的配置信息。
  • elasticJobListener:作业监听器。

SpringJobScheduler对象中封装了Elastic-Job的JobScheduler对象和Spring的ApplicationContext对象,它可以在Spring容器启动之后自动启动作业,同时在Spring容器关闭之前销毁作业。

 

🥝定义一个JobExceptionHandler,用来处理任务执行过程中的异常。在处理异常时需要根据异常类型来判断是否需要重试。

public class CustomJobExceptionHandler implements JobExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomJobExceptionHandler.class);

    @Override
    public void handleException(String jobName, Throwable cause) {
        LOGGER.error(String.format("Job [%s] exception occur in job processing", jobName), cause);
        if (cause instanceof BusinessException) {
            // 业务异常不需要重试
            return;
        } else {
            // 其他异常需要重试
   
            JobExecutionException jobExecutionException = new JobExecutionException(cause);
            jobExecutionException.setRetryable(true);
            throw jobExecutionException;
        }
    }

}

 

🥝重试机制实现

JobProperties jobProperties = new JobProperties();
jobProperties.setProperty("job_exception_handler", CustomJobExceptionHandler.class.getCanonicalName());
jobProperties.setProperty("max_time_diff_seconds", "600");
jobProperties.setProperty("misfire", "false");
jobProperties.setProperty("failover", "true");
jobProperties.setProperty("max_retries", "3");
jobProperties.setProperty("retry_interval", "1000");

JobConfig jobConfig = new JobCoreConfiguration("orderJob", cron, shardingTotalCount)
        .jobProperties(jobProperties)
        .shardingItemParameters(shardingItemParameters)
        .jobParameter(jobParameter)
        .description(description);

LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder(jobConfig)
        .overwrite(true)
        .disabled(false)
        .monitorExecution(true)
        .maxTimeDiffSeconds(10)
        .jobShardingStrategyClass(AverageAllocationJobShardingStrategy.class.getCanonicalName())
        .reconcileIntervalMinutes(30)
        .build();

🥝在Elastic-Job配置中,可以通过设置JobProperties来实现重试机制的相关配置。常用的设置包括:

  • max_retries:最大重试次数,默认为0,表示不进行重试
  • retry_interval:重试间隔时间(毫秒),默认为1000
JobProperties jobProperties = new JobProperties();
 jobProperties.setProperty("max_retries", "3");
 jobProperties.setProperty("retry_interval", "1000");

当任务执行失败时,我们需要将异常信息包装成一个JobExecutionException,并抛出。Elastic-Job会根据配置的重试次数和间隔时间进行重试。

需要注意的是,抛出的JobExecutionException需要设置retryable为true,才会进行重试。如下所示。

catch (Exception e) {
     LOGGER.error("Consume message failed", e);
     JobExecutionException jobExecutionException = new JobExecutionException(e);
     jobExecutionException.setRetryable(true);
     throw jobExecutionException;
 }


在设置retryable为true之后,Elastic-Job就会根据配置的重试次数和间隔时间进行重试,直到任务执行成功或达到最大重试次数。需要注意的是,如果任务执行时间过长,可能会影响重试机制的效果。此时可以适当调整配置中的max_time_diff_seconds参数,使其与任务执行时间相匹配。

🥝Elastic-Job实现流程:

  • 在创建作业配置信息(LiteJobConfiguration)时,需要设置分片总数和分片参数,用来将任务分配给多个作业节点处理。
  • 在创建SpringJobScheduler对象时,需要传入任务处理类、注册中心、作业配置信息和作业监听器。SpringJobScheduler对象会使用这些参数来创建作业节点,并将其注册到作业调度中心中。
  • 在作业启动后,作业调度中心会根据配置的分片总数和分片参数,将任务分配给多个作业节点并发执行。每个作业节点会接收到自己分配到的分片参数,并在执行execute方法时,获取到对应的参数进行处理。
  • 在execute方法中,@ShardingItem注解用来指定分片参数的key,Elastic-Job会通过这个key来获取当前作业节点分配到的分片参数。每个作业节点只会处理自己分配到的分片参数,不会处理其他节点的分片。