Java延迟队列

应用开发过程中,我们常常需要用到延时任务的地方,最近在工作遇到了一个需求,用UDP发送报文,发送后30s后要是还没有收到回报报文,就对对于报文进行重发。

类似于订单超时未支付取消订单一样,可以有很多解决方法,我这里采用其中一种,java的延时队列来实现。

用这篇笔记简易记录一下实现过程。

什么是DelayQueue

DelayQueue 是按照元素的延时时间排序的队列。元素必须实现 Delayed 接口,该接口定义了一个 getDelay 方法,用于返回元素的剩余延时时间。

Delayed接口继承了Comparable接口,所以延时队列中的元素对象也必须要实现compareTo方法。

延时队列在内部使用了一个优先级队列(PriorityQueue)来实现,确保队头元素始终是剩余延时时间最小的元素。

实现过程

1. 创建报文内容类
/**
 * 报文内容类
 * 报文类别号和流水号能确定唯一一条报文
 */
@Data
public class MessageInnerProtocol {
    /**
     * 报文类别号
     */
    private Integer infoClass;
    /**
     * 流水号
     */
    private Integer serialNo;
    
    // 省略其他字段
    // ......
}
2. 创建延时队列元素对象
@Getter
public class MessageDelayed implements Delayed {

    /**
     * 报文内容
     */
    private final MessageInnerProtocol message;

    /**
     * 计时开始时间
     */
    private final long startTime;
    /**
     * 超时时间
     */
    private static final long EXPIRE_TIME = 30 * 1000;

    /**
     * 构造函数
     * @param message 报文内容
     */
    public MessageDelayed(MessageInnerProtocol message) {
        this.message = message;
        this.startTime = System.currentTimeMillis();
    }


    @Override
    public long getDelay(TimeUnit unit) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        return unit.convert(System.currentTimeMillis() - elapsedTime, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        if(this == o){
            return 0;
        }
        // 根据剩余时间来进行排序
        long diff = getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
        if(diff == 0){
            return 0;
        }else if(diff < 0){
            return -1;
        }else {
            return 1;
        }
    }
}
3 报文回报状态枚举类
/**
 * 回报报文状态
 */
public enum ResultMessageStatusEnums {
    /**
     * 等待报文回报中
     */
    WAITING,
    /**
     * 成功收到报文回报,且校验成功
     */
    SUCCESS,
    /**
     * 收到回报报文,但是校验发生错误
     */
    FAIL;
}
4 创建线程不停的处理超时报文
public class MessageTimeoutDeal {

    private static final int KNOWN_CAPACITY = 16;

    /**
     * 延时队列存储报文
     */
    public static final DelayQueue<MessageDelayed> RESULT_MESSAGE_DELAY_QUEUE = new DelayQueue<>();

    /**
     * <infoClass, <serialNo, ResultMessageStatusEnums>>
     * 用保报文类别和流水号,报文状态来对报文进行处理
     */
    public static final Map<Integer, Map<Integer, ResultMessageStatusEnums>> RESULT_MESSAGE_MAP = new ConcurrentHashMap<>(KNOWN_CAPACITY);


    public static void main(String[] args) {
        // 生成报文
        MessageInnerProtocol messageInnerProtocol = new MessageInnerProtocol();
        // 设置报文内容
        messageInnerProtocol.setSerialNo(0x243);
        // 流水号建议用 AtomicInteger, 测试简易写一下
        messageInnerProtocol.setSerialNo(25);
        Map<Integer, ResultMessageStatusEnums> resultMessageMap =
                RESULT_MESSAGE_MAP.computeIfAbsent(messageInnerProtocol.getInfoClass(), k -> new ConcurrentHashMap<>(KNOWN_CAPACITY));
        // 报文状态放入map中
        resultMessageMap.put(messageInnerProtocol.getSerialNo(), ResultMessageStatusEnums.WAITING);
        // 将报文加入延时队列
        RESULT_MESSAGE_DELAY_QUEUE.add(new MessageDelayed(messageInnerProtocol));


        // 创建线程去处理延时队列中的任务
        new Thread(() -> {
            try {
                while (true){
                    // 从延时队列中获取任务
                    MessageDelayed messageDelayed = RESULT_MESSAGE_DELAY_QUEUE.take();
                    MessageInnerProtocol message = messageDelayed.getMessage();
                    Map<Integer, ResultMessageStatusEnums> resultMessageStatusEnumsMap =
                            RESULT_MESSAGE_MAP.get(message.getInfoClass());
                    // 获取到对应报文的回报状态
                    ResultMessageStatusEnums resultMessageStatusEnums = resultMessageStatusEnumsMap.get(message.getSerialNo());
                    switch (resultMessageStatusEnums){
                        case WAITING:
                        case FAIL:
                            // 没有收到回报报文或校验和失败
                            // 需要重发或其他流程
                        case SUCCESS:
                            resultMessageStatusEnumsMap.remove(message.getSerialNo());
                            break;
                        default:
                            break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }
}

测试就跳过了,需要报文处理类,对不同报文进行处理,包括回报报文,修改RESULT_MESSAGE_MAP中报文的状态。

如果需要处理大量的延迟任务, 可以使用netty的时间轮

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值