限时订单实现方案

1. 限时订单?

          在各种电商网站下订单后会保留一个时间段,时间段内未支付则自动将订单状态设置为已过期。 

2. 限时订单现象

在我们生活中处处可见限时订单的现象,如:在淘宝购物下单后没有付款,会提示多长时间订单失效;春季过年回家买火车  票,下了订单后半个小时不付款改订单就会取消;点外卖。。。

3. 解决方法一

          轮询数据库:到实现一个定时器,每隔一段时间去检查一遍数据库里的所有订单,查看其状态是否是未支付并且已经期。并修改这些数据的状态为已过期。

          优点:方法简单,容易实现

          缺点:订单状态处理不及时,轮询数据库的次数中可能很多都并没有修改订单,数据库频繁多次被连接浪费数据库资源开销,因为数据库资源非常宝贵。

          因此以上方式实际开发中基本不予采用。

4. 解决方法二

    1.采用延时队列

        采用延时队列并且与时间有关系的延时队列DelayQueue。

         实现原理:

  1.  用户下单,保存订单到数据库的同时,将该订单以及订单的过期时间推入DelayQueue;
  2.  启动一个检查订单到期的线程,该线程使用delayQueue的take()方法获取到期订单,该方法为阻塞方法,如果当前没有到期订单,该方法会一直阻塞等待,直到获取到订单后继续往下执行;
  3. 当take()获取到一个到期订单后,该线程按获取到的订单的id去数据库查询订单并去检查订单状态,如果为未支付,则将状态修改为已关闭;
  4. 当项目重启后,DelayQueue中的信息都没有了。所以项目启动扫描所有过期未支付的订单并修改为已关闭状态,扫描所有未过期未支付的订单到DelayQueue中。

   2. 代码实现     

       延时队列实体bean:             

/**
 * 
 *  延时队列实体Delayed
 * @author reyco
 *
 */
public class DelayedVo<T> implements Delayed{
	/**
	 * 过期时长/单位毫秒
	 */
	private Long expireTime;
	/**
	 * 目标对象
	 */
	private T target;
	
	public DelayedVo(Long expireTime, T target) {
		super();
		this.expireTime = expireTime+System.currentTimeMillis();
		this.target = target;
	}

	@Override
	public int compareTo(Delayed o) {
		return (int)(this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
	}

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

	public T getTarget() {
		return this.target;
	}

}

       延时订单:

/**
 * 延时订单....
 * @author reyco
 *
 */
@Service
public class DelayOrderService{
	
	/**
	 * 订单状态   1未支付 2已付款 3订单关闭 4订单完成
	 */
	/**
	 * 未支付
	 */
	private final static Integer UNPAID = 1;
	/**
	 * 订单关闭
	 */
	private final static Integer CLOSE = 3;
	
	@Autowired
	private OrderDao orderDao;
	
	private static DelayQueue<DelayedVo<OrderEntity>> delayQueue = new DelayQueue<DelayedVo<OrderEntity>>();
	/**
	 * 添加订单到DelayQueue
	 * @param orderEntity
	 * @param expireTime
	 */
	public void save(OrderEntity orderEntity,Long expireTime) {
		DelayedVo<OrderEntity> delayedVo = new DelayedVo<>(expireTime, orderEntity);
		delayQueue.put(delayedVo);
		System.out.println("订单【超时时间:"+expireTime+"毫秒】被推入延时队列,订单详情:"+orderEntity);
	}
	/**
	 * 异步线程处理DelayQueue
	 * @author reyco
	 *
	 */
	class OrderTask implements Runnable{
		@Override
		public void run() {
			try {
				while(true) {
					DelayedVo<OrderEntity> delayedVo = delayQueue.take();
					OrderEntity orderEntity = (OrderEntity)delayedVo.getTarget();
					OrderEntity selOrderEntity = orderDao.get(orderEntity.getId());
					//判断数据库中订单是否未支付
					if(selOrderEntity.getState()==UNPAID) {
						selOrderEntity.setState(CLOSE);
						System.out.println("订单关闭:order="+selOrderEntity);
						orderDao.update(selOrderEntity);
					}else {
						System.out.println("订单已处理:orderEntity="+selOrderEntity);
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	/**
	 * 启动异步线程
	 */
	@PostConstruct
	public void init() {
		new Thread(new OrderTask() ).start();
	}
	/**
	 * 启动修改过期未支付订单为已关闭状态
	 * 启动扫描数据库中的订单未过期未支付到DelayQueue
	 */
	@PostConstruct
	public void initDelayOrder() {
		//1. 处理过期未支付的订单...
		Integer count = orderDao.updateExpire();
		System.out.println("系统启动,扫描处理【"+count+"】个过期未支付的订单...");
		
		//2. 获取未过期未支付的订单
		List<OrderEntity> orders = orderDao.listOrderNoExpire();
		System.out.println("系统启动,发现【"+orders.size()+"】个未过期未支付的订单...");
		//3. 未过期未支付的订单推入延时队列
		if(null!=orders && orders.size()>0) {
			for (OrderEntity order : orders) {
				long expireTime = order.getGmtExpire().getTime()-(new Date().getTime());
				DelayedVo<OrderEntity> delayedVo = new DelayedVo<>(expireTime, order);
				delayQueue.put(delayedVo);
				System.out.println("订单【超时时间:"+expireTime+"毫秒】被推入延时队列,订单详情:"+order);
			}
		}
	}
}

        订单Service:   

/**
 * 订单Service
 * @author reyco
 *
 */
@Service
public class OrderServiceImpl implements OrderService{

	@Autowired
	private OrderDao orderedDao;
	
	@Autowired
	private DelayOrderService delayOrderService;
	
	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	public void save(OrderEntity orderEntity) {
		//订单号
		Long no = new SnowFlake(2,3).nextId();
		//超时时长
		long expireTime = 1000 * 60 * 1;
		Date gmtExpire = new Date();
		gmtExpire.setTime(System.currentTimeMillis() + expireTime);
		
		orderEntity = new OrderEnitiyBuilder()
				.builderNo(no.toString())
				.builderContent("500块钱的羽绒服。。。")
				.builderState(1)
				.builderGmtExpire(gmtExpire)
				.builderGmtDesc("备注")
				.builder();
		// 保存到数据库
		orderedDao.save(orderEntity);
		// 
		delayOrderService.save(orderEntity, expireTime);
	}

}

        订单controller:      

@RequestMapping("/api/order")
@RestController
public class OrderController {
	
	@Autowired
	private OrderService orderService;
	
	@PostMapping("save")
	public String save(@RequestBody OrderEntity orderEntity) {
		orderService.save(orderEntity);
		return "ok";
	}
}

5. 解决方法三   

       1.使用MQ的死信队列:

             实现原理:

         1.生产者发送消息到交换机(order_delay_exchange)并指定路由键order_delay_route_key),消息到达队列(order_delay_queue);

         2.队列(order_delay_queue)没有消费者,消息到期后,如果这个包含死信的队列配置了 (dead-letter-exchange=order_dead_letter_exchange)属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机(order_dead_letter_exchange)中,而这个交换机称为死信交换机;如果同时配置了(dead-letter-routing-key =order_dead_route_key)参数,则消息会由死信交换机发送到指定的队列(order_dead_queue),也就是死信队列(order_dead_queue)。

          3.消费者只需要订阅这个死信队列(order_dead_queue)就可以完成消费了。

   2.代码实现

          1)配置初始化交换机、队列:

public class MqConstant {
	/**
	 * 正常交换机
	 */
	public static final String ORDER_DELAY_EXCHANGE = "order_delay_exchange";
	/**
	 * 死信交换机
	 */
	public static final String ORDER_DEAD_EXCHANGE = "order_dead_letter_exchange";
	
	
	/**
	 * 延迟队列
	 */
	public static final String ORDER_DELAY_QUEUE = "order_delay_queue";
	/**
	 * 死信队列
	 */
	public static final String ORDER_DEAD_QUEUE = "order_dead_queue";
	
	
	/**
	 * 正常路由key
	 */
	public static final String ORDER_DELAY_ROUTE_KEY = "order_delay_route_key";
	/**
	 * 死信路由key
	 */
	public static final String ORDER_DEAD_ROUTE_KEY = "order_dead_route_key";
}
@Configuration
public class RabbitConfig {
	
	@Bean
	public DirectExchange orderDeladExchange() {
		return new DirectExchange(MqConstant.ORDER_DELAY_EXCHANGE, true, false);
	}
	@Bean
	public DirectExchange orderDeadExchange() {
		return new DirectExchange(MqConstant.ORDER_DEAD_EXCHANGE, true, false);
	}
	@Bean
	public Queue orderDeadQueue() {
		return new Queue(MqConstant.ORDER_DEAD_QUEUE, true, false, false);
	}
	@Bean
	public Queue orderDelayQueue() {
		Map<String, Object> map = new HashMap<>();
		map.put("x-dead-letter-exchange", MqConstant.ORDER_DEAD_EXCHANGE);
		map.put("x-dead-letter-routing-key", MqConstant.ORDER_DEAD_ROUTE_KEY);
		Queue queue = new Queue(MqConstant.ORDER_DELAY_QUEUE, true, false, false, map);
		return queue;
	}

	@Bean
	public Binding orderDelayQueueBinding() {
		return BindingBuilder.bind(orderDelayQueue()).to(orderDeladExchange()).with(MqConstant.ORDER_DELAY_ROUTE_KEY);
	}

	@Bean
	public Binding orderQueueDeadBinding() {
		return BindingBuilder.bind(orderDeadQueue()).to(orderDeadExchange()).with(MqConstant.ORDER_DEAD_ROUTE_KEY);
	}

}

        2)生产者:     

/**
 * 生产者
 * @author reyco
 *
 */
@Service
public class MqProducrService {
	private static final Logger logger = LoggerFactory.getLogger(MqProducrService.class);
	@Autowired
	private RabbitTemplate rabbitTemplate;

	public void send(String msg, long time, String delayQueueName) {
		// rabbit默认为毫秒级
		long times = time * 1000;
		// 后置处理器,设置过期时间
		MessagePostProcessor processor = new MessagePostProcessor() {
			@Override
			public Message postProcessMessage(Message message) throws AmqpException {
				logger.info("设置过期时间:"+times);
				message.getMessageProperties().setExpiration(String.valueOf(times));
				return message;
			}
		};
		logger.info("【发送消息】:"+msg);
		rabbitTemplate.setConfirmCallback((correlationData,ack,cause)->{
			if(ack) {
				logger.info("【发送消息】消息发送成功");
			}else {
				logger.info("【发送消息】消息发送失败:");
			}
		});
		rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey)->{
			logger.info("返回消息回调:{} 应答代码:{} 回复文本:{} 交换器:{} 路由键:{}", message, replyCode, replyText, exchange, routingKey);
		});
		rabbitTemplate.convertAndSend(MqConstant.ORDER_DELAY_EXCHANGE, MqConstant.ORDER_DELAY_ROUTE_KEY, msg, processor);
	}

}

       3.消费者

/**
 * 消费者
 * @author reyco
 *
 */
@Service
public class MqOrderConsumerService {

	private static final Logger logger = LoggerFactory.getLogger(MqOrderConsumerService.class);

	@Autowired
	private OrderDao orderDao;

	@RabbitHandler
	@RabbitListener(queues = MqConstant.ORDER_DEAD_QUEUE)
	public void process(String msg, Channel channel, Message message) throws InterruptedException {
		try {
			logger.info("【消息消费】延迟时间到,开始执行, {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
			Order order = (Order) JsonUtils.jsonToObj(msg, Order.class);
			if (order != null && order.getState() == 1) {
				order.setState(3);
				orderDao.update(order);
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			}
			logger.info("【消息消费】消息消费成功, {},{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),msg);
		} catch (IOException e) {
			logger.info("【消息消费】消息消费失败, {},{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),msg);
		}
	}
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

java的艺术

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

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

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

打赏作者

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

抵扣说明:

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

余额充值