说明:关于使用rabbitmq实现订单超时的部分说明有错误,首先mq是可以实现自定义超时时间的,我们可以在创建队列queue.ordercreate时不设置它的x-message-ttl参数,转而在代码里设置消息过期时间。
public void test() { // 消息后处理对象,设置一些消息的参数信息 MessagePostProcessor messagePostProcessor = message -> { //1.设置message的信息 // 第二个方法:消息的过期时间 ,5秒之后过期,这里可以自由设置消息过期时间 message.getMessageProperties().setExpiration("5000"); //2.返回该消息 return message; }; rabbitTemplate.convertAndSend("exchange.ordertimeout", "*", "hello!", messagePostProcessor); }
=========================================================
先描述一下业务场景,用户下单后在规定时间内没有完成支付,那么系统需要把订单终结掉。
但是这个规定时间可能不是定死的,它可能是3小时,2小时,30分钟等等
个人的实现思路
一、轮询数据库
这种方式就是在保存订单的时候把订单的超时时间也一起保存进去,然后用定时任务去轮询数据库获取未支付的订单,再去判断是否超时了。
但是!这种方法太捞了呀,而且也不具备实时性,比如我有个订单号为:123的订单是在10:00:00这个时间超时,但是我的定时任务是每5分钟执行一次,它又恰好是在09:58:00执行了一次任务,那么它这个时候拿到订单号:123的订单做判断,结果是未超时。然后下一次执行是在10:03:00,这个时候再拿到123的订单,肯定是超时了。也就是说我们订单应该在10:00:00就超时的,可直到10:03:00才超时。这就是没有实时性。 这咱都不说数据量太多处理起来还贼慢的情况。
二、mq延迟消息
第二种方式就是借助消息队列,这里我只提供rabbitMQ的实现思路。
使用rabbitMQ实现延迟消息首先要了解两个点:消息的TTL和死信Exchange。通过这两个我们就可以实现延迟消息了。
TTL(Time To Live)
消息的ttl意思就是消息的过期时间。rabbitmq中可以对消息和队列分别设置过期时间。
对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。
死信Exchange(Dead Letter Exchanges)
死信交换机不是一个消息队列了,而是一个交换机,交换机可以绑定多个消息队列。
当消息满足以下情况时,消息会进入死信队列:
- 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。
- 消息的TTL到期了,消息过期了。
- 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信交换机上。
死信交换机并不是一个特殊的交换机,它也是一个很普通的交换机,只是因为它的作用不同,所以称呼也不同。
在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。
具体实现步骤:
1.创建死信交换器 exchange.ordertimeout (fanout)
2.创建队列queue.ordertimeout
3.建立死信交换器 exchange.ordertimeout 与队列queue.ordertimeout 之间的绑定
4.创建队列queue.ordercreate,Arguments添加:
x-message-ttl=10000
x-dead-letter-exchange: exchange.ordertimeout
代码层面实现:
我们每次创建订单的时候要往queue.ordercreate这里消息队列里方法消息,可以将订单编号作为消息发送。
在写一个监听类,去监听queue.ordertimeout这个消息队列,当“queue.ordercreate”里的消息过期时会被转发到
“queue.ordertimeout”里
这个时候拿到“queue.ordertimeout”里的消息(订单编号),做一个超时处理就行了。
小结:
用rabbitmq作为中间件可以解决很多后端服务的问题,而且是低耦合。
如果你的业务场景要设置所有订单都是固定好的时间内过期,比如都是在2小时内过期,那么很简单,只需要把对应的“x-message-ttl”的值设置成“7200000”。这种情况下用rabbitmq很合适。
回到开头,我们说可能这个时间也不是固定死的,它可能是3小时,2小时,30分钟等等。如果不多,可以多开几个延迟队列,对应不同的过期时间。
如果设置过期时间的自由度很高,用rabbitmq咋实现,以我目前的道行,我实现不了,哈哈哈哈哈哈!
接下的方案就是针对那种自定义过期时间的!
三、redis的过期事件
redis作为中间件有多好用,咱就不多BB里。直接上代码!
第一步、 首先写一个redis的配置类:
@Configuration public class RedisListenerConfig { @Autowired private RedisTemplate redisTemplate; /** * 处理乱码 */ @Bean public RedisTemplate redisTemplate() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } @Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); return container; } }
第二步、再写一个监听类,这个类需要继承KeyExpirationEventMessageListener类,并且重写onMessage方法。
@Component public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } /** * 针对redis数据失效事件处理,进行数据处理 * * @param message * @param pattern */ @Override public void onMessage(Message message, byte[] pattern) { //获取失效的key String expirationKey = message.toString(); //对开头是ordertimeout的键进行处理 if (expirationKey.startsWith("ordertimeout:")) { String[] split = expirationKey.split(":"); String orderId = split[1]; //处理订单 this.endOrder(orderId); } } }
第三步、创建订单的时候存入redis里,key的格式:“ordertimeout:订单号”,value:“订单过期时间"
/** * 设置订单超时 * * @param time * @param orderId */ public void setTimeOut(LocalDateTime time, String orderId) { //获取截止时间 LocalDateTime endTime = time; Date endDate = Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()); //系统当前时间,计算秒值 Date nowDate = new Date(); //相减获取秒 long expirationTime = (endDate.getTime() - nowDate.getTime()) / 1000; redisTemplate.boundValueOps("ordertimeout:" + orderId).set(endTime, expirationTime, TimeUnit.SECONDS); }
最后我们就可以在监听类里处理过期的订单了。
四、总结
以上三种方案,第一种个人认为应该没有人会用,就连我自己也觉得是废话。后面两种方案可以参考着来,延迟消息适合那种固定死时间的场景,redis的过期事件适合那种灵活设置过期时间的场景。