DelayQueue延迟队列和Redis缓存实现订单自动取消功能

DelayQueue延迟队列和Redis缓存实现订单自动取消功能

前言

一、加入redis依赖

二、开始撸代码

1.订单队列对象主要记录订单id和订单失效时间

2.编写队列业务层

3.创建线程池,用于订单创建的时候将订单id加入到队列中

4.编写Redis业务层,主要用来将订单存入缓存和便利缓存对象到队列中

5.编写redis业务层实现类

6. 考虑到系统宕机后会将队列中的数据删除掉,服务器重启后数据消失的情况,下面加入监听器,在服务启动时去redis中查找未支付的订单并重新加入到队列中,前提是redis的数据没有被删除

7. 订单创建成功之后将订单id和超时时间放入队列和redis中

8. 订单支付成功之后从队列和redis中移除订单信息


前言

最近项目中需要使用到订单倒计时取消功能,刚开始是想到使用一个定时器去跑,每隔多少时间去数据查,然后在根据订单创建时间计算出是否要过期在一一改变订单状态,而这种方式虽然说能可以解决这个问题,但是在资源上就会比较损耗,我接下来的是使用jdk自带的延时队列DelayQueue和redis来实现,redis相信大家都比较熟悉了,DelayQueue大家可以自行百度。


一、加入redis依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

二、开始撸代码

1.订单队列对象主要记录订单id和订单失效时间

代码如下:

package com.ctb.obd.order.time;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

@Data
@ApiModel(description = "订单队列对象")
public class DshOrder implements Delayed {
    @ApiModelProperty(value = "订单id")
    private String orderId;
    @ApiModelProperty(value = "超时时间")
    private long startTime;
    /**
     * orderId:订单id
     * timeout:自动取消订单的超时时间,秒
     * */
    public DshOrder(String orderId, int timeout){
        this.orderId = orderId;
        this.startTime = System.currentTimeMillis() + timeout*1000L;
    }
    @Override
    public int compareTo(Delayed other) {
        if (other == this){
            return 0;
        }
        if(other instanceof DshOrder){
            DshOrder otherRequest = (DshOrder)other;
            long otherStartTime = otherRequest.getStartTime();
            return (int)(this.startTime - otherStartTime);
        }
        return 0;
    }

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

}

2.编写队列业务层

代码如下:

package com.ctb.obd.order.time;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.DelayQueue;

@Slf4j
@Service
@Data
public class DelayService {
    private boolean start ;
    private OnDelayedListener listener;
    private DelayQueue<DshOrder> delayQueue = new DelayQueue<DshOrder>();

    public static interface OnDelayedListener{
        public void onDelayedArrived(DshOrder order);
    }

    public void start(OnDelayedListener listener){
        if(start){
            return;
        }
        log.error("DelayService 启动");
        start = true;
        this.listener = listener;
        new Thread(new Runnable(){
            @Override
            public void run(){
                try{
                    while(true){
                        DshOrder order = delayQueue.take();
                        if(DelayService.this.listener != null){
                            DelayService.this.listener.onDelayedArrived(order);
                        }
                    }
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public void add(DshOrder order){
        delayQueue.put(order);
    }

    public void remove(String orderId){
        DshOrder[] array = delayQueue.toArray(new DshOrder[]{});
        if(array == null || array.length <= 0){
            return;
        }
        DshOrder target = null;
        for(DshOrder order : array){
            if(order.getOrderId() == orderId){
                target = order;
                break;
            }
        }
        if(target != null){
            delayQueue.remove(target);
        }
    }

}

3.创建线程池,用于订单创建的时候将订单id加入到队列中

代码如下:

package com.ctb.obd.order.time;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.*;

public class ThreadPoolUtils {

    private final ExecutorService executor;

    private static ThreadPoolUtils instance = new ThreadPoolUtils();

    private ThreadPoolUtils() {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("order-runner-%d").build();
        int size = Runtime.getRuntime().availableProcessors() * 2;
        this.executor = new ThreadPoolExecutor(size,size,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),namedThreadFactory);
    }

    public static ThreadPoolUtils getInstance() {
        return instance;
    }

    public static <T> Future<T> execute(final Callable<T> runnable) {
        return getInstance().executor.submit(runnable);
    }

    public static Future<?> execute(final Runnable runnable) {
        return getInstance().executor.submit(runnable);
    }


}

4.编写Redis业务层,主要用来将订单存入缓存和便利缓存对象到队列中

代码如下:

package com.ctb.obd.order.service;
import com.ctb.obd.order.model.Order;

import java.util.Set;

public interface OrderRedisService {
    /**
     * 订单对象加入缓存
     *
     * @param orderId     订单id
     * @param orderObject 订单对象
     */
    void saveOrder(String orderId, Order orderObject);

    /**
     * 获得缓存订单对象
     *
     * @param orderId 订单id
     * @return
     */
    String getOrder(String orderId);

    /**
     * 删除缓存订单对象
     *
     * @param orderId 订单id
     */
    void deleteOrder(String orderId);

    /**
     * 查询所有需要缓存的订单对象
     *
     * @return
     */
    Set<String> sacn();

    /**
     * 获得redis键的剩余时间
     *
     * @param key redis键
     * @return 剩余时间
     */
    Long getSurplusTime(String key);

}

5.编写redis业务层实现类

代码如下:

package com.ctb.obd.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.ctb.obd.order.model.Order;
import com.ctb.obd.order.service.OrderRedisService;
import com.ctb.obd.order.time.DelayService;
import com.ctb.obd.order.time.DshOrder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class OrderRedisServiceImpl implements OrderRedisService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Resource
    private DelayService delayService;

    /**
     * 保存订单并设置过期时间
     * @param outTradeId
     * @param redisDo
     */
    @Override
    public void saveOrder(String outTradeId, Order redisDo) {
        String key = outTradeId;
        //key过期时间为120分钟
        redisTemplate.opsForValue().set(key, JSON.toJSONString(redisDo), 120, TimeUnit.MINUTES);
        //加入队列
        delayService.add(new DshOrder(redisDo.getId().toString(), 7200));
    }

    /**
     * 获取订单
     * @param outTradeNo
     * @return
     */
    @Override
    public String getOrder(String outTradeNo) {
        String key = outTradeNo;
        String message = redisTemplate.opsForValue().get(key);
        if(message != null){
            return key;
        }
        return "";
    }

    /**
     * 删除订单
     * @param outTradeNo
     */
    @Override
    public void deleteOrder(String outTradeNo) {
        String key = outTradeNo;
        redisTemplate.delete(key);
    }

    /**
     * 获取订单中所有的key
     * @return
     */
    @Override
    public Set<String> sacn(){
        Set<String> execute = redisTemplate.execute(new RedisCallback<Set<String>>() {
            @Override
            public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
                Set<String> binaryKeys = new HashSet<>();
                Cursor<byte[]> cursor = connection.scan( new ScanOptions.ScanOptionsBuilder().match("order*").count(100).build());
                while (cursor.hasNext()) {
                    binaryKeys.add(new String(cursor.next()));
                }
                return binaryKeys;
            }
        });
        return execute;
    }

    @Override
    public Long getSurplusTime(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
}

6. 考虑到系统宕机后会将队列中的数据删除掉,服务器重启后数据消失的情况,下面加入监听器,在服务启动时去redis中查找未支付的订单并重新加入到队列中,前提是redis的数据没有被删除

代码如下:

package com.ctb.obd.order.time;

import com.ctb.obd.order.service.OrderRedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

import java.util.Set;

@Slf4j
@Service
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    DelayService delayService;
    @Autowired
    OrderRedisService redisService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent evt) {
        log.info(">>>>>>>>>>>>系统启动完成,onApplicationEvent()");

        //自动取消订单
        delayService.start(new DelayService.OnDelayedListener(){
            @Override
            public void onDelayedArrived(final DshOrder order) {
                //异步来做
                ThreadPoolUtils.execute(new Runnable(){
                    @Override
                    public void run(){
                        String orderId = order.getOrderId();
                        //查库判断是否需要自动取消订单
                        int surpsTime = redisService.getSurplusTime(orderId).intValue();
                        log.info("redis键:" + orderId + ";剩余过期时间:"+surpsTime);
                        if(surpsTime > 0){
                            log.info("没有需要取消的订单!");
                        }else{
                            log.info("自动取消订单,删除队列:"+orderId);
                            //从队列中删除
                            delayService.remove(orderId);
                            //从redis删除
                            redisService.deleteOrder("order"+orderId);
                            log.info("自动取消订单,删除redis:"+orderId);
                            //todo 对订单进行取消订单操作 修改订单状态

                        }
                    }
                });
            }
        });
        //查找需要入队的订单
        ThreadPoolUtils.execute(new Runnable(){
            @Override
            public void run() {
                log.info("查找需要入队的订单");
                Set<String> keys = redisService.sacn();
                if(keys == null || keys.size() <= 0){
                    return;
                }
                log.info("需要入队的订单keys:"+keys);
                log.info("写到DelayQueue");
                for(String key : keys){
                    String orderKey = redisService.getOrder(key);
                    int surpsTime = redisService.getSurplusTime(key).intValue();
                    log.info("读redis,key:"+key);
                    log.info("redis键:" + key + ";剩余过期时间:"+surpsTime);
                    if(orderKey != null){
                        DshOrder dshOrder = new DshOrder(orderKey,surpsTime);
                        delayService.add(dshOrder);
                        log.info("订单自动入队:"+dshOrder);
                    }
                }
            }
        });
    }
}

7. 订单创建成功之后将订单id和超时时间放入队列和redis中

代码如下:

//订单生成之后把订单插入到待取消的队列和redis
        ThreadPoolUtils.execute(new Runnable() {
            @Override
            public void run() {
                String itrOrderId = "order" + orderId;
                //1 插入到待收货队列
                DshOrder dshOrder = new DshOrder(itrOrderId, 600);
                delayService.add(dshOrder);
                log.error("订单order" + orderId + "入队列");
                //2插入到redis
                orderRedisService.saveOrder(itrOrderId, orderObject);
                log.error("订单order" + orderId + "入redis缓存");
            }
        });

8. 订单支付成功之后从队列和redis中移除订单信息

代码如下:

String delOrderId = "order" + orderId;
        int surpsTime = orderRedisService.getSurplusTime(delOrderId).intValue();
        log.error("redis键:" + delOrderId + ";剩余过期时间:"+surpsTime);
        if (surpsTime <= 0) {
            delayService.remove(delOrderId);
            log.error("订单手动出队:" + delOrderId);
            orderRedisService.deleteOrder(delOrderId);
            log.error("订单手动出redis:" + delOrderId);
        }

以上就是今天讲的内容,也可以使用单独使用DelayQueue延迟队列完成,只不过加了redis之后防止服务器宕机数据丢失的情况。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
延迟队列是一种特殊的队列,它可以让元素在一定时间后自动队列。在Java中,可以使用DelayQueue类来实现延迟队列。有了延迟队列,我们就可以很方便地实现自动关单功能。 具体实现步骤如下: 1. 定义一个订单类,该类实现Delayed接口,重写getDelay和compareTo方法,以便在延迟队列中进行排序和比较。 2. 创建一个延迟队列对象,并将需要自动关闭的订单对象放入队列中。 3. 创建一个线程,不断从延迟队列中取出已经到期的订单,执行关闭操作。 具体代码示例如下: ```java import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; // 订单类,实现Delayed接口 public class Order implements Delayed { private String orderId; private long expireTime; public Order(String orderId, long delayTime) { this.orderId = orderId; this.expireTime = System.currentTimeMillis() + delayTime; } public String getOrderId() { return orderId; } // 计算延迟时间 @Override public long getDelay(TimeUnit unit) { return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } // 比较大小,用于排序 @Override public int compareTo(Delayed o) { long diff = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS); return diff > 0 ? 1 : (diff < 0 ? -1 : 0); } } // 自动关闭订单线程 public class CloseOrderThread extends Thread { private DelayQueue<Order> delayQueue; public CloseOrderThread(DelayQueue<Order> delayQueue) { this.delayQueue = delayQueue; } @Override public void run() { while (true) { try { // 从延迟队列中取出已经到期的订单 Order order = delayQueue.take(); System.out.println("订单" + order.getOrderId() + "已经超时,自动关闭。"); // 执行关闭操作 // ... } catch (InterruptedException e) { e.printStackTrace(); } } } } // 测试代码 public class Test { public static void main(String[] args) { // 创建延迟队列 DelayQueue<Order> delayQueue = new DelayQueue<>(); // 添加订单延迟队列delayQueue.put(new Order("1001", 3000)); delayQueue.put(new Order("1002", 5000)); delayQueue.put(new Order("1003", 7000)); // 创建自动关闭订单线程,启动线程 CloseOrderThread closeOrderThread = new CloseOrderThread(delayQueue); closeOrderThread.start(); } } ``` 在上面的代码中,我们定义了一个订单类Order,它实现Delayed接口,并重写了getDelay和compareTo方法。然后我们创建了一个延迟队列DelayQueue,并将需要自动关闭的订单对象放入队列中。最后,我们创建了一个自动关闭订单的线程CloseOrderThread,它不断从延迟队列中取出已经到期的订单,并执行关闭操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cstpoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值