常見的延時任務方案
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、訊息可靠性較高(基於定時任務輪詢實現的資料庫持久化)
這個比較簡單,自己實現吧