常見的定時任務方案

QQ-emoji發表於2024-07-16

常見的延時任務方案

1、最輕量級(基於記憶體的執行緒池實現)一般用於短時間實時性較高,容許少量訊息丟失


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.*;

@Slf4j
@Component
public class MessageSender {

    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    public void sendMessageWithDelay(String message, long delayInSeconds) {
        scheduler.schedule(() -> {
            try {
                log.info("資料訊息補償,message={},delayTime={}",message,delayInSeconds);
                //todo 做業務操作
            }catch (Exception e){
                log.error("資料訊息補償,brand={},orderNo={}",message,delayInSeconds,e);
            }
        }, delayInSeconds, TimeUnit.SECONDS);
    }
}

2、輕量級(基於Redission的實現)推薦 一般用於實時性,訊息可靠性,輕量級的綜合選擇

引入依賴

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.9.1</version>
        </dependency>

訊息釋出器


import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@Component
public class RedissionDelayMessageSender {

    private final RedissonClient redissonClient;
    /**
     *  釋出延時訊息
     *  @param param
     *  @param delayTime
     */
    public void publishDelayMsg(JSONObject param,long delayTime) {
        RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque("delay-channel-queue");
        RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        int retryNum = Integer.parseInt(String.valueOf(param.getOrDefault("retryNum", "0"))) + 1;
        log.info("當前回撥重試次數={}", retryNum);
        param.put("retryNum", String.valueOf(retryNum));
        param.put("msg", param.getString("msg"));
        delayedQueue.offer(param.toJSONString(), delayTime, TimeUnit.SECONDS);
        log.info("延時訊息傳送成功,delayTime={},data={}", delayTime, param);
    }
}

訊息處理器


import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName: MsgHandler
 * @Author: lmy
 * @Description:
 * @Version: 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RedissionDelayMessageHandler {

    private final RedissonClient redissonClient;
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @PostConstruct
    public void startListener() {
        threadPoolTaskExecutor.execute(this::handle);
    }

    public void handle(){
        RBlockingDeque<String> blockingDeque = redissonClient.getBlockingDeque("delay-channel-queue");
        while (true) {
            try {
                String data = blockingDeque.poll(2, TimeUnit.SECONDS);
                log.info("監聽到延遲訊息:{}", data);
                if (StringUtils.isNotBlank(data)) {
                    JSONObject jsonObject = JSONObject.parseObject(data);
                    int retryNum = Integer.parseInt(String.valueOf(jsonObject.getOrDefault("retryNum", "0")));
                    if (retryNum < 3){
                        jsonObject.put("retryNum",retryNum);
                        //todo 做業務
                    }else {
                        log.warn("重試已達最大次數3,退出重試,人工介入,param={}",data);
                    }
                }
            } catch (Exception e){
                log.error("監聽延遲釋出訊息失敗", e);
            }
        }
    }
}

3、訊息可靠性高(基於MQ技術實現的延時訊息)推薦 一般用於訊息可靠性較高,不允許丟失


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @Author:
 */
@Slf4j
@Component
public class RabbitMqDelayMessageSender {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RabbitHandler
    @RabbitListener(queues = "queue-name")
    public void listener(JSONObject entity, Channel channel, Message message) throws IOException {
        String databaseName = entity.getString("database");
        String tableName = entity.getString("table");
        JSONArray data = entity.getJSONArray("data");
        //todo 做業務
        sendDelayMessage(data.toJSONString(), 10000L);
        //訊息手動簽收
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
    
    /**
     * 傳送延遲訊息
     *
     * @param msg 訊息體
     * @param delayTime  延遲時間
     */
    public void sendDelayMessage(String msg, Long delayTime) {
        rabbitTemplate.convertAndSend("delay-exchange", "delay-key", msg, message -> {
            //預設接收毫秒
            message.getMessageProperties().setHeader("x-delay", delayTime);
            return message;
        });
        log.info("延遲訊息傳送成功,data={},delayTime={},",msg,delayTime );
    }
}

4、訊息可靠性較高(基於定時任務輪詢實現的資料庫持久化)
這個比較簡單,自己實現吧