1.使用场景
需要完成一个定时任务,而这些任务的数量并不多
2.对比mq,数据库定时查询优劣
LinkedBlockingQueue
优点:
- 线程安全:LinkedBlockingQueue是一个线程安全的阻塞队列,提供了线程间安全的队列操作。
- 有界性:LinkedBlockingQueue是一个可选有界队列,可以指定队列的容量,防止内存溢出。
- 阻塞功能:当队列满时,添加元素的操作会被阻塞;当队列空时,获取元素的操作也会被阻塞,这对于需要同步的生产者-消费者模型非常有用。
- 先进先出(FIFO):队列按照先进先出的规则对元素进行排序。
缺点:
- 内存占用:由于是内存中的数据结构,如果数据量过大,会占用大量内存。
- 持久性:LinkedBlockingQueue不提供数据的持久化存储,如果系统崩溃或重启,数据可能会丢失。
数据库定时查询
优点:
- 持久性:数据库中的数据是持久化存储的,即使系统崩溃或重启,数据也不会丢失。
- 可扩展性:数据库可以轻松地扩展存储容量和处理能力。
- 查询功能:数据库提供了强大的查询功能,可以根据各种条件检索数据。
缺点:
- 性能开销:定时查询数据库可能会产生较大的性能开销,特别是在高并发场景下。
- 实时性:定时查询的实时性较低,无法即时获取数据变化。
- 复杂性:需要编写额外的代码来实现定时查询和数据处理逻辑。
MQ(消息队列)
优点:
- 异步通信:MQ允许多个处理程序并行处理消息,减轻应用程序的负载并提高系统的吞吐量。
- 可靠性:MQ通常支持事务性操作,确保消息的可靠传递和失败重试。
- 解耦:MQ可以将应用程序的不同部分解耦,使它们能够以松散或独立的方式进行通信。
- 可扩展性:MQ支持在分布式系统中进行扩展,允许系统之间的通信跨越不同的节点和网络。
缺点:
- 复杂性:使用MQ需要一定的技术知识和配置,可能会增加系统的复杂性。
- 资源占用:处理大量的消息需要大量的内存和磁盘空间,可能会对系统资源产生压力。
- 不适合实时性要求高的场景:MQ主要用于异步通信,不支持实时性要求较高的场景。
归纳
- LinkedBlockingQueue 适用于内存中的数据同步和传递,具有线程安全性和阻塞功能,但需要注意内存占用和持久性问题。
- 数据库定时查询 适用于需要持久化存储和查询的场景,但可能会产生性能开销和实时性问题。
- MQ 适用于跨系统、跨网络的异步通信场景,具有可靠性、解耦和可扩展性等优点,但需要注意复杂性和资源占用问题。
在实际应用中,应根据具体需求和场景选择合适的方案。如果需要在内存中同步数据并考虑阻塞功能,可以选择LinkedBlockingQueue;如果需要持久化存储和查询数据,可以选择数据库定时查询;如果需要在分布式系统中进行异步通信,可以考虑使用MQ。
3.代码
3.1 延时工具类
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
import java.util.function.Consumer;
@Slf4j
public class DelayedQueueUtil {
private static final BlockingQueue<String> DELAY_QUEUE = new LinkedBlockingQueue<>();
private static final ScheduledExecutorService SCHEDULER = Executors.newSingleThreadScheduledExecutor();
// 添加延迟队列对象
public static void addDelayedQueueObject(String name, long delayMillis) {
log.info("订单号{},进入延迟队列,如果超时则自动取消剩余,时间剩余{}s,之后将会自动取消", name, delayMillis);
SCHEDULER.schedule(() -> {
try {
DELAY_QUEUE.put(name); // 将对象放入队列,可能会阻塞直到有空间可用
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 处理中断,可能是优雅地退出
}
}, delayMillis * 1000, TimeUnit.MILLISECONDS);
}
// 订阅阻塞队列
public static void subscribeBlockingQueue(Consumer<String> consumer) {
new Thread(() -> {
while (true) {
try {
String name = DELAY_QUEUE.take(); // 从队列中取出对象,可能会阻塞直到有元素可用
consumer.accept(name); // 处理对象
} catch (InterruptedException e) {
// 处理中断,可能是优雅地退出线程
Thread.currentThread().interrupt();
}
}
}).start();
}
}
3.2 生产者
DelayedQueueUtil.addDelayedQueueObject(appOrder.getOrderId(),payTime-time.getSeconds());
3.3消费者
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.function.Consumer;
@Component
@RequiredArgsConstructor
@Slf4j
public class DelayConsumer implements Consumer<String> {
@Override
public void accept(String orderId) {
}
}
3.4 项目启动自动订阅
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* 启动后订阅延迟队列
*/
@Component
@Slf4j
public class MyStartupRunner implements CommandLineRunner {
@Autowired
private DelayConsumer delayConsumer;
@Override
public void run(String... args) throws Exception {
try {
// 调用MyToolClass的方法或其他初始化逻辑
DelayedQueueUtil.subscribeBlockingQueue(delayConsumer);
log.info("success subscribe delayConsumer");
} catch (Exception e) {
log.info("fail subscribe delayConsumer");
}
try {
// 由于此延迟队列是基于内存的,每次重启则需要读取数据库数据,并重新发送到延迟队列中,在此需要写重新载入的方法
log.info("success leading not pay order");
} catch (Exception e) {
log.info("fail leading not pay order");
}
}
}
4.备注
对于此方法的性能上,或者功能上有见解。请在评论区进行评论。我会进行学习修改。