使用LinkedBlockingQueue实现延迟功能

1.使用场景

        需要完成一个定时任务,而这些任务的数量并不多

2.对比mq,数据库定时查询优劣

LinkedBlockingQueue

优点

  1. 线程安全:LinkedBlockingQueue是一个线程安全的阻塞队列,提供了线程间安全的队列操作。
  2. 有界性:LinkedBlockingQueue是一个可选有界队列,可以指定队列的容量,防止内存溢出。
  3. 阻塞功能:当队列满时,添加元素的操作会被阻塞;当队列空时,获取元素的操作也会被阻塞,这对于需要同步的生产者-消费者模型非常有用。
  4. 先进先出(FIFO):队列按照先进先出的规则对元素进行排序。

缺点

  1. 内存占用:由于是内存中的数据结构,如果数据量过大,会占用大量内存。
  2. 持久性:LinkedBlockingQueue不提供数据的持久化存储,如果系统崩溃或重启,数据可能会丢失。

数据库定时查询

优点

  1. 持久性:数据库中的数据是持久化存储的,即使系统崩溃或重启,数据也不会丢失。
  2. 可扩展性:数据库可以轻松地扩展存储容量和处理能力。
  3. 查询功能:数据库提供了强大的查询功能,可以根据各种条件检索数据。

缺点

  1. 性能开销:定时查询数据库可能会产生较大的性能开销,特别是在高并发场景下。
  2. 实时性:定时查询的实时性较低,无法即时获取数据变化。
  3. 复杂性:需要编写额外的代码来实现定时查询和数据处理逻辑。

MQ(消息队列)

优点

  1. 异步通信:MQ允许多个处理程序并行处理消息,减轻应用程序的负载并提高系统的吞吐量。
  2. 可靠性:MQ通常支持事务性操作,确保消息的可靠传递和失败重试。
  3. 解耦:MQ可以将应用程序的不同部分解耦,使它们能够以松散或独立的方式进行通信。
  4. 可扩展性:MQ支持在分布式系统中进行扩展,允许系统之间的通信跨越不同的节点和网络。

缺点

  1. 复杂性:使用MQ需要一定的技术知识和配置,可能会增加系统的复杂性。
  2. 资源占用:处理大量的消息需要大量的内存和磁盘空间,可能会对系统资源产生压力。
  3. 不适合实时性要求高的场景: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.备注

        对于此方法的性能上,或者功能上有见解。请在评论区进行评论。我会进行学习修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值