java延时队列
应用场景
1)7天自动收货
a、用户支付完成以后,把订单ID插入到内存的一个DelayQueue中,同时插入到Redis中。
b、7天之内,用户点击了确认收货,则从DelayQueue中删除,从Redis中删除。
c、超过7天,DelayQueue中的订单ID出队,查询数据库,改状态为自动收货,删除redis。
d、如果7天之内,web服务器重启过,则web服务器启动以后,从redis中读取待收货的订单,插入到DelayQueue。
2)30分钟未付款自动取消订单
一、写一个JedisUtil,用来操作redis
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set; import javax.annotation.PostConstruct; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.weaver.patterns.ThisOrTargetPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import com.aqh.util.MyProperties;
import com.sun.org.glassfish.external.statistics.Statistic; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; /**
* jedis缓存工具
*/
@Service("jedisUtil")
public class JedisUtil {
private JedisPool pool; @Autowired
private MyProperties properties; private static Log log = LogFactory.getLog(JedisUtil.class); private JedisUtil() {
} @SuppressWarnings("unused")
@PostConstruct // 指定spring实例化对象之后调用的方法
private void init() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxActive(Integer.parseInt(properties.getJedisMaxActive()));
config.setMaxIdle(Integer.parseInt(properties.getJedisMaxIdle()));
config.setMaxWait(Long.parseLong(properties.getJedisMaxWait()));
config.setTestOnBorrow(false);
pool = new JedisPool(new JedisPoolConfig(), properties.getJedisHost(),
Integer.parseInt(properties.getJedisPort()),
Integer.parseInt(properties.getJedisTimeout()));
} public void set(String key, String value) {
Jedis jedis = this.getResource();
try {
jedis.set(key, value);
} finally {
this.returnResource(jedis);
}
} public String get(String key) {
Jedis jedis = this.getResource();
try {
return jedis.get(key);
} finally {
this.returnResource(jedis);
}
} public void setObject(String key, Object obj) {
Jedis jedis = this.getResource();
try {
jedis.set(key.getBytes(), serialize(obj));
} finally {
this.returnResource(jedis);
}
}
public Object getObject(String key) { Jedis jedis = this.getResource();
try {
if(jedis.get(key.getBytes()) == null) {
return null;
} else {
return unserialize(jedis.get(key.getBytes()));
}
} finally {
this.returnResource(jedis);
}
}
/**
* 删除key
* @param key
*/
public void delkey(String...keys) {
Jedis jedis = this.getResource();
try {
jedis.del(keys);
} finally {
this.returnResource(jedis);
}
} /**
* 设置hash的值
* @param key hash中的key
* @param field hash中的域
* @param obj 值
*/
public void setHash(String key,String field,Object obj) {
Jedis jedis = this.getResource();
try {
jedis.hset(key.getBytes(), field.getBytes(), serialize(obj));
} finally {
this.returnResource(jedis);
}
}
/**
* 查找redis中hash的value值
* @param key hash中的key
* @param field hash中的域
* @return 返回对象
*/
public Object getHash(String key,String field) {
Jedis jedis = this.getResource();
try {
if (jedis.hget(key, field) == null) {
return null;
}
return unserialize(jedis.hget(key.getBytes(), field.getBytes()));
} finally {
this.returnResource(jedis);
}
} /**
* 删除hash中的指定域
* @param key
* @param fields
* @return
*/
public Long removeHash(String key,String fields) {
Jedis jedis = this.getResource();
try { return jedis.hdel(key.getBytes(),fields.getBytes()); } finally {
this.returnResource(jedis);
}
} /**
* 返回hash中的所有域
* @param key
*/
public Set<String> hKeys(String key) {
Jedis jedis = this.getResource();
try {
Set<String> hkeys = jedis.hkeys(key);
return hkeys;
} finally {
this.returnResource(jedis);
}
}
/**
* 序列化
* @param object
* @return
*/
private static byte[] serialize(Object object) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
// 序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
return bytes;
} catch (Exception e) {
e.printStackTrace();
log.error("jedis序列化异常.....");
}
return null;
} /**
* 反序列化
*
* @param bytes
* @return
*/
private static Object unserialize(byte[] bytes) {
ByteArrayInputStream bais = null;
try {
// 反序列化
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
log.info("jedis反序列化异常.....");
}
return null;
} /**
* 获取jedis
* @return
*/
private Jedis getResource() {
Jedis jedis = pool.getResource();
jedis.auth(properties.getJedisPassword());
return jedis;
} /**
* 设置生命周期(过期时间)
* @param key
* @param second
*/
public void setExpireByKey(String key, int seconds) {
Jedis jedis = null;
try {
jedis = this.getResource();
jedis.expire(key, seconds);
} catch (Exception e) {
log.error(e);
} finally {
this.returnResource(jedis);
}
} /**
* 获取某个Key的余下存活时间(秒)。
* @param key
* @return 存活时间(秒)
*/
public long getTimeToLive(String key) {
Jedis jedis = null;
long sec = -2;
try {
jedis = this.getResource();
sec = jedis.ttl(key);
} catch (Exception e) {
log.error(e);
} finally {
this.returnResource(jedis);
}
return sec;
} /**
* jedis放回连接池
* @param jedis
*/
private void returnResource(Jedis jedis) {
pool.returnResource(jedis);
} /**
* 释放Redis资源池。
*/
public void destroy() {
if(pool != null) {
pool.destroy();
}
log.info("Redis池已销毁");
}
}
二、线程池的工具类
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class ThreadPoolUtils { private final ExecutorService executor; private static ThreadPoolUtils instance = new ThreadPoolUtils(); private ThreadPoolUtils() {
this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
} 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);
}
}
三、要加入延时队列的对象,需要实现Delayed类
package com.aqh.util; import java.io.Serializable;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit; import javax.print.attribute.standard.MediaSize.Other; import sun.util.logging.resources.logging; /**
* 订单队列对象
* @author Administrator
*
*/
public class DshOrder implements Delayed,Serializable{ private String orderNo;//订单号 private long startTime; // 超时时间 /**
* 构造方法
*/
public DshOrder() {} public DshOrder(String orderNo, long timeout) {
this.orderNo = orderNo;
this.startTime = System.currentTimeMillis() + timeout;
} @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);
} public String getOrderNo() {
return orderNo;
} public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
} public long getStartTime() {
return startTime;
} public void setStartTime(long startTime) {
this.startTime = startTime;
} @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DshOrder other = (DshOrder) obj;
if (orderNo.equals(other.getOrderNo()))
return false;
if (startTime != other.startTime)
return false;
return true;
} @Override
public int hashCode() {
int result = 17;
result = result * 31 + (int)orderNo.hashCode();
result = result * 31 + (int)startTime;
return result;
} @Override
public String toString() {
return "DSHOrder [orderNo=" + orderNo + ", startTime=" + startTime + "]";
}
}
四、延时队列服务类
import java.util.concurrent.DelayQueue; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import com.aqh.bean.btc.BtcConstant;
import com.aqh.bean.btc.BtcOrder;
import com.aqh.dao.IBtcMemberDao;
import com.aqh.service.IBtcMemberService;
import com.aqh.util.DshOrder;
import com.aqh.util.JedisUtil;
/**
* 延时队列service
* @author Administrator
*
*/
@Service
public class DelayService {
private boolean start;//判断是否启动队列 private OnDelayedListener listener;//内部接口监听器 private DelayQueue<DshOrder> delayQueue = new DelayQueue<DshOrder>(); //队列集合 private Log log = LogFactory.getLog(DelayService.class); @Autowired
private JedisUtil jedisUtil; @Autowired
private IBtcMemberService btcMemberService; public static interface OnDelayedListener{
public void onDelayedArrived(DshOrder order);
} public void start(OnDelayedListener listener) {
if (start) {
log.error(">>>>>>>>>>>>DelayService已经在启动状态");
return;
}
log.info(">>>>>>>>>>>>DelayService 启动");
start = true;
this.listener = listener;
new Thread(new Runnable() { @Override
public void run() {
try {
while(true) {
log.info("*********准备获取延迟队列里面将要取消的队列*******");
/* 延时队列会将加入队列中的元素按照过期时间的先后顺序排序,先过期的在队首,该take方法会判断队首
* 元素是否过期,如果没过期,会阻塞等待,直到队首元素过期,才会取出来,往下执行逻辑 */
DshOrder order = delayQueue.take();
log.info("*********订单"+order.getOrderNo()+"已经超过30分钟,被自动取消*******");
//修改订单状态
//根据订单号查询订单,判断状态是否已经完成
BtcOrder btcOrder = btcMemberService.getOrderByNo(order.getOrderNo());
if (btcOrder.getStatus() == 1 ) {
//取消订单改变状态并对相应的库存进行相加
btcMemberService.updateOrderAndStock(order.getOrderNo(),0);
}
/* 这里的类名.this是为了区分那个类的this,一般在内部类中,需要调用外部类的this的时候使用,不加类名,
* 直接this代表当前类,内部类中代表内部类,外部类中调用代表外部类,这里不再内部类中,也可以显示的指明是
* 哪个类的this*/
if (DelayService.this.listener != null) {
DelayService.this.listener.onDelayedArrived(order);
}
}
} catch (Exception e) {
e.printStackTrace();
} }
}).start();
} public void add(DshOrder order){
//写入队列
delayQueue.put(order);
//存入redis
jedisUtil.setHash(BtcConstant.ORDER_CONFIRM, order.getOrderNo(), order);
log.info("**************订单号:" + order.getOrderNo() + "被写入订单成功!*************");
}
/**
* 重载主要是为了业务中只需要写入延时队列,而不需要写入redis的情况
* @param order 延时订单
* @param type null
*/
public void add(DshOrder order,String type){
//写入队列
delayQueue.put(order);
//存入redis
//jedisUtil.setHash(BtcConstant.ORDER_SHIP, order.getOrderNo(), order);
} public boolean remove(DshOrder order){
//从redis中删除
jedisUtil.removeHash(BtcConstant.ORDER_CONFIRM, order.getOrderNo()); log.info("**************订单号:" + order.getOrderNo() + "被删除成功!*************");
//从队列里面删除
return delayQueue.remove(order); } public void remove(String orderNo){
DshOrder[] array = delayQueue.toArray(new DshOrder[]{});
if(array == null || array.length <= 0){
return;
}
DshOrder target = null;
for(DshOrder order : array){
if(order.getOrderNo().equals(orderNo)){
target = order;
break;
}
}
if(target != null){
this.remove(target);
}
}
}
五、需要写一个spring监听器,系统启动完需要执行如下两个操作
1)启动延时队列的服务线程,去循环取要过期的队首元素。(调用延时队列的take阻塞方法)
2)线程池中运行一个线程,在每次启动时从redis中将未过期的对象重新加入到延时队列中,因为延时队列是基于内存的,宕机后延时队列就不存在了,所以需要redis等数据库配合使用,每次加入延时队列中的对象,都需要加入redis中,从延时队列中删除的对象,也最好从redis中删除,这样宕机后
未过期的延时队列中的对象就在redis中,每次启动服务器,线程就会从redis中将所有对象重新加入到延时队列中。
import java.util.Set; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service; import com.aqh.bean.btc.BtcConstant;
import com.aqh.service.delayQueue.DelayService.OnDelayedListener;
import com.aqh.util.DshOrder;
import com.aqh.util.JedisUtil; /**
* 用于监听延时队列的类
* @author Administrator
* spring监听器必须加上@Service,注入到bean对象中
*/
@Service
public class StartupListener implements ApplicationListener<ContextRefreshedEvent>{ private static final Log log = LogFactory.getLog(StartupListener.class); @Autowired
private DelayService delayService; @Autowired
private JedisUtil jedisUtil; @Override
public void onApplicationEvent(ContextRefreshedEvent evt) {
log.info(">>>>>>>>>>>>系统启动完成,onApplicationEvent");
/* applicationontext和使用MVC之后的webApplicationontext会两次调用监听器的方法,
* 这样可以解决,applicationontext是父容器,所以没有父级元素,这句代表父容器(applicationontext)直接返回,不执行
* 监听器方法,子容器(springMVC的)才会执行后面的监听器方法,这样就不会两次调用了*/
if (evt.getApplicationContext().getParent() == null) {
return;
} delayService.start(new OnDelayedListener() { @Override
public void onDelayedArrived(final DshOrder order) {
ThreadPoolUtils.execute(new Runnable() { @Override
public void run() {
String orderNo = order.getOrderNo();
//查库判断是否需要进行删除
log.info("30分钟自动取消订单,onDelayedArrived():" + orderNo);
delayService.remove(order);
}
}); }
}); //查找需要入队的订单
ThreadPoolUtils.execute(new Runnable() { @Override
public void run() {
log.info("查找需要入队的订单");
Set<String> orderNos = jedisUtil.hKeys(BtcConstant.ORDER_CONFIRM);
log.info("30分钟未支付需要入队的订单:" + orderNos);
if (orderNos == null || orderNos.size() <= 0) {
return;
} //写到DelayQueue
for (String str : orderNos) {
//通过redis取key中的str域的value
DshOrder dshOrder = (DshOrder) jedisUtil.getHash(BtcConstant.ORDER_CONFIRM, str);
//存入延时队列里面
delayService.add(dshOrder, null);
}
}
}); } }
以上的步骤已经将延时队列写完了,会根据传入延时队列的对象过期时间(虽然上面写的日志都是30分钟,但是过期时间是根据加入队列时加的时间决定的),自动到期后出队列,执行操作;
具体调用的地方:
1)下单后需要加入延时队列,添加过期时间为30分钟,30分钟后未付款自动取消订单
2)发货后,需要加入延时队列,添加过期时间为7天。
7天内用户点击确认收货按钮,调用延时队列服务类的remove方法,从DelayQueue中删除,从Redis中删除;
超过7天,DelayQueue中的订单ID出队,查询数据库,改状态为自动收货,删除redis。
注意:在延时队列的逻辑操作中,两种情况可以在延时对象中加入标志判断,是30天自动取消,还是7天自动确认收货,对应的执行不同的逻辑,然后从redis中删除 ;
调用的代码如下:
//加入延时队列和redis缓存中
/* 创建延时对象时,传入订单号和过期时间(单位为毫秒) */
DshOrder dshOrder = new DshOrder(orderNo,BtcConstant.ORDER_CONFIRM_TIMEOUT);
delayService.add(dshOrder);
参考链接:https://blog.csdn.net/goldenfish1919/article/details/50923450
java延时队列的更多相关文章
- java实现rabbitMQ延时队列详解以及spring-rabbit整合教程
在实际的业务中我们会遇见生产者产生的消息,不立即消费,而是延时一段时间在消费.RabbitMQ本身没有直接支持延迟队列功能,但是我们可以根据其特性Per-Queue Message TTL和 Dead ...
- java并发编程工具类JUC第三篇:DelayQueue延时队列
DelayQueue 是BlockingQueue接口的实现类,它根据"延时时间"来确定队列内的元素的处理优先级(即根据队列元素的"延时时间"进行排序).另一层 ...
- 🏆【Java技术专区】「延时队列专题」教你如何使用【精巧好用】的DelayQueue
延时队列前提 定时关闭空闲连接:服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之. 定时清除额外缓存:缓存中的对象,超过了空闲时间,需要从缓存中移出. 实现任务超时处理:在网络协议滑动窗口请求 ...
- java中延时队列的使用
最近遇到这么一个需求,程序中有一个功能需要发送短信,当满足某些条件后,如果上一步的短信还没有发送出去,那么应该取消这个短信的发送.在翻阅java的api后,发现java中有一个延时队列可以解决这个问题 ...
- Java 延迟队列使用
延时队列,第一他是个队列,所以具有对列功能第二就是延时,这就是延时对列,功能也就是将任务放在该延时对列中,只有到了延时时刻才能从该延时对列中获取任务否则获取不到…… 应用场景比较多,比如延时1分钟发短 ...
- Redis学习笔记之延时队列
目录 一.业务场景 二.Redis延时队列 一.业务场景 所谓延时队列就是延时的消息队列,下面说一下一些业务场景比较好理解 1.1 实践场景 订单支付失败,每隔一段时间提醒用户 用户并发量的情况,可以 ...
- Redis简单延时队列
Redis实现简单延队列, 利用zset有序的数据结构, score设置为延时的时间戳. 实现思路: 1.使用命令 [zrangebyscore keyName socreMin socreMax] ...
- RabbitMQ学习之延时队列
原帖参考:http://www.cnblogs.com/telwanggs/p/7124687.html?utm_source=itdadao&utm_medium=referral http ...
- RabbitMQ进阶使用-延时队列的配置(Spring Boot)
依赖 MAVEN配置pom.xml <dependency> <groupId>org.springframework.boot</groupId> <art ...
随机推荐
- python 学习之路(1)
1变量的使用以及原理 先定义一个变量 变量的类型 变量的命名 01变量的命名 变量名 = 值 左边是变量名 右边是值 又叫做赋值 上面是ipython的交互模式的 那我们看看在pycharm里面如何输 ...
- python学习之路(20)
装饰器 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数. >>> def now(): print('2019.0519') >>> ...
- 01.二维数组中的查找 (Java)
题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...
- Mac-连接Windows远程桌面软件
链接:https://download.csdn.net/download/ab601026460/9885775 https://blog.csdn.net/ab601026460/article/ ...
- leetcode-easy-listnode-19 remove nth node from end of list
mycode 88.29% 关键是一定要head前新建一个节点,否则要分类讨论很多次来避免slow或者fast出现None.next的错误 # Definition for singly-linke ...
- python模块------pyautogui
安装 pip install pyautogui 基本使用 查询 screenWidth, screenHeight = pyautogui.size() # 屏幕尺寸 mouseX, mouseY ...
- 用Python将二进制文件转化为数组并以文件形式存储
最近在学习Python,发现Python语言非常适合文件的批处理操作.本文将介绍一下用Python如何实现将一个二进制bin文件转化为一个常量数组的.c文件存储起来.这为我们在一些没有文件系统不能调用 ...
- 转 实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化(三)
这是本系列的最后一篇,主要是select_related() 和 prefetch_related() 的最佳实践. 第一篇在这里 讲例子和select_related() 第二篇在这里 讲prefe ...
- Computer Network Homework2’s hard question
Computer Network Homework2’s hard question 2. What is the signal which is used to modulate the origi ...
- IPython基础使用_Round2
目录 目录 前言 软件环境 Ipython的字符串处理 Ipython的魔力函数Magic lsmagic Output所有魔力函数 查看Magic的源码 env 显示系统环境变量 history 查 ...