task-DelayQueue-订单超时实例(task任务,DelayQueue阻塞队列)

本文介绍如何使用 Spring Task 进行订单超时处理的设计与实现,通过配置定时任务,结合 DelayQueue 和 ExecutorService 实现对订单状态的监听与处理。
很早就对task,queue有兴趣,今天总结一下,做个笔记。

一、对于多任务异步的项目中,task的作用很普遍,最近学习,和小试牛刀了一下,有一些感悟,做个笔记。
二、使用spring task,配置如下:

<bean id="orderTimeoutService"
        class="com.haohao.order.service.order.impl.OrderTimeoutServiceImpl"
        init-method="init" destroy-method="destroy">
        <property name="handlers">
            <map key-type="com.haohao.order.common.enums.TimeoutTypeEnum">
                <entry key="ORDER" value-ref="orderTimeoutHandler" />
                <entry key="REFUND" value-ref="refundTimeoutHandler" />
            </map>
        </property>
        <property name="loadTimeoutTask" value="${timeout.task.is.load}"/>
    </bean>

    <bean id="orderTimeoutHandler"
        class="com.haohao.order.service.timeout.handler.OrderTimeoutHandler">
        <property name="timeoutActions">
            <map>
                <entry key="wait_buyer_pay_deposit" value-ref="closeOrderAction" />
                <entry key="wait_buyer_pay_all" value-ref="closeOrderAction" />
                <entry key="wait_seller_send" value-ref="sellerSendGoodsAction" />
                <entry key="wait_buyer_receive" value-ref="receiveGoodsAction" />
            </map>
        </property>
    </bean>

    <bean id="refundTimeoutHandler"
        class="com.haohao.order.service.timeout.handler.RefundTimeoutHandler">
        <property name="timeoutActions">
            <map>
                <entry key="wait_seller_agree" value-ref="confirmRefundProtocolAction" />
                <entry key="wait_buyer_modify" value-ref="closeRefundAction" />
                <entry key="wait_buyer_send" value-ref="closeRefundAction" />
                <entry key="wait_seller_receive" value-ref="refundAction" />
                <entry key="wait_customer_service" value-ref="refundCustomerServiceAction" />
            </map>
        </property>
    </bean>

<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="orderTimeoutService" method="loadRecentlyTimeoutInfo"   cron="${load.order.timeout.period}" />
</task:scheduled-tasks>

    <task:scheduler id="myScheduler" pool-size="5" />

Java代码:

/**
 * 
 */
package com.haohao.order.service.order.impl;

import java.util.List;
import java.util.Map;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;

import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.haohao.order.common.enums.TimeoutTypeEnum;
import com.haohao.order.common.util.BeanUtils;
import com.haohao.order.dal.timeout.OrderTimeoutInfoDAO;
import com.haohao.order.dal.timeout.OrderTimeoutInfoDO;
import com.haohao.order.service.order.OrderTimeoutService;
import com.haohao.order.service.timeout.handler.TimeoutHandler;
import com.haohao.order.service.timeout.model.TimeoutModel;

/**
 * 功能描述:
 * 
 * @author mandy.hu
 */

public class OrderTimeoutServiceImpl implements OrderTimeoutService {
    private static final Logger logger = LoggerFactory.getLogger(OrderTimeoutServiceImpl.class);

    @Resource
    private OrderTimeoutInfoDAO orderTimeoutInfoDAO;

    private DelayQueue<TimeoutModel> timeoutQueue;
    @Autowired(required = false)
    private ExecutorService executorService;
    private Map<TimeoutTypeEnum, TimeoutHandler> handlers;
    private transient boolean running;

    private boolean isLoadTimeoutTask;

    protected void init() {
        if(isLoadTimeoutTask) {
            logger.info("初始化超时队列");
            running = true;
            //初始化一个阻塞队列
            timeoutQueue = new DelayQueue<TimeoutModel>();

            startHandler();
        }
    }
    //这个方法就是用来处理业务的,意思是查询超时的订单,并放在DelayQueue中。
    @Override
    public void loadRecentlyTimeoutInfo() {
        if(isLoadTimeoutTask) {
            logger.info("加载即将超时的信息...");

            List<OrderTimeoutInfoDO> orderTimeoutInfoDOs = orderTimeoutInfoDAO.queryRecentlyOrderTimeoutInfos();

            logger.info("加载即将超时的信息...loadRecentlyTimeoutInfo  orderTimeoutInfoDOs:{}",orderTimeoutInfoDOs);

            List<TimeoutModel> timeoutModels = BeanUtils.convertList(orderTimeoutInfoDOs, TimeoutModel.class);

            if (CollectionUtils.isNotEmpty(timeoutModels)) {
                timeoutQueue.addAll(timeoutModels);
            }
            logger.info("加载即将超时的信息...loadRecentlyTimeoutInfo timeoutQueue:{}",timeoutQueue);
        }
    }

    @PreDestroy
    protected void destroy() {
        running = false;
    }

    private void startHandler() {
        logger.info("启动超时监听线程");

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (running) {
                    try {
                    //阻塞队列执行take()方法,删除队列顶部的一个对象,并返回删除的对象。这个方法是阻塞方法,如果队列中没有对象,这个线程将被阻塞,知道队列中有对象。
                        TimeoutModel timeoutModel = timeoutQueue.take();
//对象交给ExecutorService处理,ExecutorService是一个异步处理机制,相当于一个线程池,意思就把这个take()的对象委托给ExecutorService处理。
                        getExecutorService().execute(new TimeoutHandlerTask(timeoutModel));
                    } catch (InterruptedException e) {
                        logger.warn("处理超时失败", e);
                        e.printStackTrace();
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    class TimeoutHandlerTask implements Runnable {
        private TimeoutModel timeoutModel;

        public TimeoutHandlerTask(TimeoutModel timeoutModel) {
            this.timeoutModel = timeoutModel;
        }

        @Override
        public void run() {
            TimeoutHandler handler = handlers.get(timeoutModel.getTimeoutType());

            if (handler != null) {
                handler.onTimeout(timeoutModel);
            }
        }

    }

    public ExecutorService getExecutorService() {
        if (executorService == null) {
        //得到executorService 对象,其中Runtime.getRuntime()相当于new一个对象,、、Executors.newFixedThreadPool(xxx)最大执行多少个线程的executorService 。
            executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2);
        }

        return executorService;
    }

    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public Map<TimeoutTypeEnum, TimeoutHandler> getHandlers() {
        return handlers;
    }

    public void setHandlers(Map<TimeoutTypeEnum, TimeoutHandler> handlers) {
        this.handlers = handlers;
    }

    public void setLoadTimeoutTask(boolean isLoadTimeoutTask) {
        this.isLoadTimeoutTask = isLoadTimeoutTask;
    }

}

三:上面注释已经很清楚了,为了防止以后不认识自己写注释,在这里再系统的说一下流程。

  1. 在spring配置文件中,配置task。
<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="orderTimeoutService" method="loadRecentlyTimeoutInfo"  cron="${load.order.timeout.period}" />
    </task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="5" />

其中ref=”orderTimeoutService”是要执行的类,method=”loadRecentlyTimeoutInfo”是要执行这个类中的方法,cron=”${load.order.timeout.period}” 是执行规则多长时间执行,什么周期执行,都可以灵活设置。

  1. 配置执行的类:
<bean id="orderTimeoutService"
        class="com.haohao.order.service.order.impl.OrderTimeoutServiceImpl"
        init-method="init" destroy-method="destroy">
        <property name="handlers">
            <map key-type="com.haohao.order.common.enums.TimeoutTypeEnum">
                <entry key="ORDER" value-ref="orderTimeoutHandler" />
                <entry key="REFUND" value-ref="refundTimeoutHandler" />
            </map>
        </property>
        <property name="loadTimeoutTask" value="${timeout.task.is.load}"/>
    </bean>

    <bean id="orderTimeoutHandler"
        class="com.haohao.order.service.timeout.handler.OrderTimeoutHandler">
        <property name="timeoutActions">
            <map>
                <entry key="wait_buyer_pay_deposit" value-ref="closeOrderAction" />
                <entry key="wait_buyer_pay_all" value-ref="closeOrderAction" />
                <entry key="wait_seller_send" value-ref="sellerSendGoodsAction" />
                <entry key="wait_buyer_receive" value-ref="receiveGoodsAction" />
            </map>
        </property>
    </bean>

    <bean id="refundTimeoutHandler"
        class="com.haohao.order.service.timeout.handler.RefundTimeoutHandler">
        <property name="timeoutActions">
            <map>
                <entry key="wait_seller_agree" value-ref="confirmRefundProtocolAction" />
                <entry key="wait_buyer_modify" value-ref="closeRefundAction" />
                <entry key="wait_buyer_send" value-ref="closeRefundAction" />
                <entry key="wait_seller_receive" value-ref="refundAction" />
                <entry key="wait_customer_service" value-ref="refundCustomerServiceAction" />
            </map>
        </property>
    </bean>

OrderTimeoutServiceImpl类中有两个方法init-method=”init” destroy-method=”destroy”,一个是这个类加载时就会执行的,另一个是这个类销毁时执行的,init-method方法做一些初始化操作。在这个类中有一个属性,是一个map,名字是handlers,handlers中有初始化了两个对象,本文重点不是这里,就不多赘述了

  1. 看看这一个类的具体实现,首先是初始化方法。
protected void init() {
        if(isLoadTimeoutTask) {
            logger.info("初始化超时队列");
            running = true;
            timeoutQueue = new DelayQueue<TimeoutModel>();

            startHandler();
        }
    }

这个方法很简单就是new一个阻塞队列DelayQueue。之后执行startHandler()方法,

private void startHandler() {
        logger.info("启动超时监听线程");

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (running) {
                    try {
                        TimeoutModel timeoutModel = timeoutQueue.take();

                        getExecutorService().execute(new TimeoutHandlerTask(timeoutModel));
                    } catch (InterruptedException e) {
                        logger.warn("处理超时失败", e);
                        e.printStackTrace();
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

这个方法目的是开起监听,利用的是DelayQueue阻塞队列的特性,方法中调用take()方法,这个方法是阻塞的方法,意思是移除队列中顶部的对象,并返回这个对象,如果队列是空,就会一直等待,等待队列中有对象,线程也处于阻塞的状态,同事就起到了监听的作用。getExecutorService().execute(new TimeoutHandlerTask(timeoutModel))这个方法是把timeoutModel 对象委托给ExecutorService()处理,这边好处理下一个任务。

public ExecutorService getExecutorService() {
        if (executorService == null) {
            executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2);
        }

        return executorService;
    }

这个就是得到ExecutorService对象, ExecutorService是一个异步处理机制,Executors.newFixedThreadPool(Num);最大能处理Num个线程的ExecutorService。调用ExecutorService的execute()方法就是委托了。

public void loadRecentlyTimeoutInfo() {
        if(isLoadTimeoutTask) {
            logger.info("加载即将超时的信息...");

            List<OrderTimeoutInfoDO> orderTimeoutInfoDOs = orderTimeoutInfoDAO.queryRecentlyOrderTimeoutInfos();

            logger.info("加载即将超时的信息...loadRecentlyTimeoutInfo  orderTimeoutInfoDOs:{}",orderTimeoutInfoDOs);

            List<TimeoutModel> timeoutModels = BeanUtils.convertList(orderTimeoutInfoDOs, TimeoutModel.class);

            if (CollectionUtils.isNotEmpty(timeoutModels)) {
                timeoutQueue.addAll(timeoutModels);
            }
            logger.info("加载即将超时的信息...loadRecentlyTimeoutInfo timeoutQueue:{}",timeoutQueue);
        }
    }

这个方法就是在spring task中配置要执行的方法,刚开始的时候一直在想在哪里有数据放在了DelayQueue中,就是这里,将想要监听的数据放在DelayQueue中,在startHandler()方法中就能监听到,这是task就是一个闭环了。

### 实现原理 Java 的 `DelayQueue` 是一个无界阻塞队列,用于存放实现了 `Delayed` 接口的对象。队列中的元素只有在其延迟时间到达后才能被取出。这种特性非常适合用来实现订单超时自动取消的功能。 在商城系统中,当用户创建一个订单但未支付时,可以将该订单放入 `DelayQueue` 中,并设置一个过期时间(例如 30 分钟)。一旦超过这个时间,订单就会从队列中被取出,并触发取消操作。 ### 数据结构定义 首先,需要定义一个类来表示订单,并实现 `Delayed` 接口。此类包含订单编号和过期时间,并提供计算剩余时间和比较的方法。 ```java import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class OrderAutoEntity implements Delayed { private String orderNo; private long expire; public static final long EXPIRE_TIME = TimeUnit.MINUTES.toMillis(30); public OrderAutoEntity(String orderNo, LocalDateTime orderTime) { this.orderNo = orderNo; long creatTime = orderTime.toInstant(ZoneOffset.of("+8")).toEpochMilli(); this.expire = creatTime + EXPIRE_TIME; } @Override public long getDelay(TimeUnit unit) { return unit.convert(expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed other) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), other.getDelay(TimeUnit.MILLISECONDS)); } public String getOrderNo() { return orderNo; } } ``` ### 处理逻辑 接下来,需要编写一个任务来处理 `DelayQueue` 中的订单。这个任务会不断地从队列中取出到期的订单,并执行取消操作。 ```java import java.util.concurrent.DelayQueue; public class OrderExpiryTask implements Runnable { private DelayQueue<OrderAutoEntity> delayQueue; public OrderExpiryTask(DelayQueue<OrderAutoEntity> delayQueue) { this.delayQueue = delayQueue; } @Override public void run() { try { while (!Thread.interrupted()) { // 取出到期的订单并处理 OrderAutoEntity expiredOrder = delayQueue.take(); System.out.println("Canceling order: " + expiredOrder.getOrderNo()); // 这里可以添加实际的取消订单逻辑 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } ``` ### 启动任务 最后,在系统启动时,创建一个 `DelayQueue` 实例,并启动一个或多个线程来处理到期的订单。 ```java import java.time.LocalDateTime; import java.util.concurrent.DelayQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class OrderTimeoutManager { private static final int POOL_SIZE = 1; public static void main(String[] args) { DelayQueue<OrderAutoEntity> delayQueue = new DelayQueue<>(); // 启动处理到期订单任务 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(POOL_SIZE); executorService.scheduleAtFixedRate(new OrderExpiryTask(delayQueue), 0, 1, TimeUnit.SECONDS); // 模拟添加订单到队列中 String orderNo = "ORDER_001"; LocalDateTime now = LocalDateTime.now(); OrderAutoEntity order = new OrderAutoEntity(orderNo, now); delayQueue.put(order); System.out.println("Order added: " + orderNo); } } ``` ### 相关问题
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值