最近项目中需要使用到订单倒计时取消功能,刚开始是想到使用一个定时器去跑,每隔多少时间去数据查,然后在根据订单创建时间计算出是否要过期在一一改变订单状态,而这种方式虽然说能可以解决这个问题,但是在资源上就会比较损耗,我接下来的是使用jdk自带的延时队列DelayQueue和redis来实现,redis相信大家都比较熟悉了,DelayQueue大家可以自行百度。
提示:以下是本篇文章正文内容,下面案例可供参考
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
代码如下:
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);
}
}
代码如下:
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);
}
}
}
代码如下:
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);
}
}
代码如下:
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);
}
代码如下:
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);
}
}
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);
}
}
}
});
}
}
//订单生成之后把订单插入到待取消的队列和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缓存");
}
});
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之后防止服务器宕机数据丢失的情况,下期我们使用RabbitMQ 延时队列和死信队列完成订单倒计时取消的功能