領(lǐng)導(dǎo):誰再用定時任務(wù)關(guān)單咱揍,立馬滾蛋

在電商颖榜、支付等領(lǐng)域,往往會有這樣的場景煤裙,用戶下單后放棄支付了掩完,那這筆訂單會在指定的時間段后進(jìn)行關(guān)閉操作,細(xì)心的你一定發(fā)現(xiàn)了像某寶硼砰、某東都有這樣的邏輯且蓬,而且時間很準(zhǔn)確,誤差在1s內(nèi)题翰;那他們是怎么實(shí)現(xiàn)的呢恶阴?一般的做法有如下幾種

1诈胜、定時任務(wù)關(guān)閉訂單

2、rocketmq延遲隊列

3冯事、rabbitmq死信隊列

4焦匈、時間輪算法

5、redis過期監(jiān)聽

一昵仅、定時任務(wù)關(guān)閉訂單

一般情況下缓熟,最不推薦的方式就是關(guān)單方式就是定時任務(wù)方式,原因我們可以看下面的圖來說明


我們假設(shè)摔笤,關(guān)單時間為下單后10分鐘够滑,定時任務(wù)間隔也是10分鐘;通過上圖我們看出籍茧,如果在第1分鐘下單版述,在第20分鐘的時候才能被掃描到執(zhí)行關(guān)單操作,這樣誤差達(dá)到10分鐘寞冯,這在很多場景下是不可接受的,另外需要頻繁掃描主訂單號造成網(wǎng)絡(luò)IO和磁盤IO的消耗晚伙,對實(shí)時交易造成一定的沖擊吮龄,所以PASS

二、rocketmq延遲隊列方式

延遲消息

生產(chǎn)者把消息發(fā)送到消息服務(wù)器后咆疗,并不希望被立即消費(fèi)漓帚,而是等待指定時間后才可以被消費(fèi)者消費(fèi),這類消息通常被稱為延遲消息午磁。

在RocketMQ開源版本中尝抖,支持延遲消息,但是不支持任意時間精度的延遲消息迅皇,只支持特定級別的延遲消息昧辽。

消息延遲級別分別為1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,共18個級別登颓。

發(fā)送延遲消息(生產(chǎn)者)

/**

? ? * 推送延遲消息

? ? * @param topic

? ? * @param body

? ? * @param producerGroup

? ? * @return boolean

? ? */

? ? public boolean sendMessage(String topic, String body, String producerGroup)

? ? {

? ? ? ? try

? ? ? ? {

? ? ? ? ? ? Message recordMsg = new Message(topic, body.getBytes());

? ? ? ? ? ? producer.setProducerGroup(producerGroup);

? ? ? ? ? ? //設(shè)置消息延遲級別搅荞,我這里設(shè)置14,對應(yīng)就是延時10分鐘

? ? ? ? ? ? // "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"

? ? ? ? ? ? recordMsg.setDelayTimeLevel(14);

? ? ? ? ? ? // 發(fā)送消息到一個Broker

? ? ? ? ? ? SendResult sendResult = producer.send(recordMsg);

? ? ? ? ? ? // 通過sendResult返回消息是否成功送達(dá)

? ? ? ? ? ? log.info("發(fā)送延遲消息結(jié)果:======sendResult:{}", sendResult);

? ? ? ? ? ? DateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

? ? ? ? ? ? log.info("發(fā)送時間:{}", format.format(new Date()));

? ? ? ? ? ? return true;

? ? ? ? }

? ? ? ? catch (Exception e)

? ? ? ? {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? log.error("延遲消息隊列推送消息異常:{},推送內(nèi)容:{}", e.getMessage(), body);

? ? ? ? }

? ? ? ? return false;

? ? }

消費(fèi)延遲消息(消費(fèi)者)

/**

? ? * 接收延遲消息

? ? *

? ? * @param topic

? ? * @param consumerGroup

? ? * @param messageHandler

? ? */

? ? public void messageListener(String topic, String consumerGroup, MessageListenerConcurrently messageHandler)

? ? {

? ? ? ? ThreadPoolUtil.execute(() ->

? ? ? ? {

? ? ? ? ? ? try

? ? ? ? ? ? {

? ? ? ? ? ? ? ? DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();

? ? ? ? ? ? ? ? consumer.setConsumerGroup(consumerGroup);

? ? ? ? ? ? ? ? consumer.setVipChannelEnabled(false);

? ? ? ? ? ? ? ? consumer.setNamesrvAddr(address);

? ? ? ? ? ? ? ? //設(shè)置消費(fèi)者拉取消息的策略框咙,*表示消費(fèi)該topic下的所有消息咕痛,也可以指定tag進(jìn)行消息過濾

? ? ? ? ? ? ? ? consumer.subscribe(topic, "*");

? ? ? ? ? ? ? ? //消費(fèi)者端啟動消息監(jiān)聽,一旦生產(chǎn)者發(fā)送消息被監(jiān)聽到喇嘱,就打印消息茉贡,和rabbitmq中的handlerDelivery類似

? ? ? ? ? ? ? ? consumer.registerMessageListener(messageHandler);

? ? ? ? ? ? ? ? consumer.start();

? ? ? ? ? ? ? ? log.info("啟動延遲消息隊列監(jiān)聽成功:" + topic);

? ? ? ? ? ? }

? ? ? ? ? ? catch (MQClientException e)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? log.error("啟動延遲消息隊列監(jiān)聽失敗:{}", e.getErrorMessage());

? ? ? ? ? ? ? ? System.exit(1);

? ? ? ? ? ? }

? ? ? ? });

? ? }

實(shí)現(xiàn)監(jiān)聽類耗帕,處理具體邏輯

/**

* 延遲消息監(jiān)聽

*

* @author hucj

*/

@Component

public class CourseOrderTimeoutListener implements ApplicationListener<ApplicationReadyEvent>

{

? ? @Resource

? ? private MQUtil mqUtil;

? ? @Resource

? ? private CourseOrderTimeoutHandler courseOrderTimeoutHandler;

? ? @Override

? ? public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent)

? ? {

? ? ? ? // 訂單超時監(jiān)聽

? ? ? ? mqUtil.messageListener(EnumTopic.ORDER_TIMEOUT, EnumGroup.ORDER_TIMEOUT_GROUP, courseOrderTimeoutHandler);

? ? }

}

/**

*? 實(shí)現(xiàn)監(jiān)聽

*

* @author hucj

*/

@Slf4j

@Component

public class CourseOrderTimeoutHandler implements MessageListenerConcurrently

{

? ? @Override

? ? public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {

? ? ? ? for (MessageExt msg : list)

? ? ? ? {

? ? ? ? ? ? // 得到消息體

? ? ? ? ? ? String body = new String(msg.getBody());

? ? ? ? ? ? JSONObject userJson = JSONObject.parseObject(body);

? ? ? ? ? ? TCourseBuy courseBuyDetails = JSON.toJavaObject(userJson, TCourseBuy.class);

? ? ? ? ? ? // 處理具體的業(yè)務(wù)邏輯糖权,繁疤,,沽翔,,

DateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

? ? ? ? log.info("消費(fèi)時間:{}", format.format(new Date()));


? ? ? ? return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;

? ? }

}

這種方式相比定時任務(wù)好了很多惠奸,但是有一個致命的缺點(diǎn)闰集,就是延遲等級只有18種(商業(yè)版本支持自定義時間),如果我們想把關(guān)閉訂單時間設(shè)置在15分鐘該如何處理呢科汗?

三藻烤、rabbitmq死信隊列的方式

Rabbitmq本身是沒有延遲隊列的,只能通過Rabbitmq本身隊列的特性來實(shí)現(xiàn)头滔,想要Rabbitmq實(shí)現(xiàn)延遲隊列怖亭,需要使用Rabbitmq的死信交換機(jī)(Exchange)和消息的存活時間TTL(Time To Live)

死信交換機(jī)

一個消息在滿足如下條件下,會進(jìn)死信交換機(jī)坤检,記住這里是交換機(jī)而不是隊列兴猩,一個交換機(jī)可以對應(yīng)很多隊列。

一個消息被Consumer拒收了早歇,并且reject方法的參數(shù)里requeue是false倾芝。也就是說不會被再次放在隊列里,被其他消費(fèi)者使用箭跳。

上面的消息的TTL到了晨另,消息過期了。

隊列的長度限制滿了谱姓。排在前面的消息會被丟棄或者扔到死信路由上借尿。

死信交換機(jī)就是普通的交換機(jī),只是因?yàn)槲覀儼堰^期的消息扔進(jìn)去屉来,所以叫死信交換機(jī)路翻,并不是說死信交換機(jī)是某種特定的交換機(jī)

消息TTL(消息存活時間)

消息的TTL就是消息的存活時間。RabbitMQ可以對隊列和消息分別設(shè)置TTL茄靠。對隊列設(shè)置就是隊列沒有消費(fèi)者連著的保留時間茂契,也可以對每一個單獨(dú)的消息做單獨(dú)的設(shè)置。超過了這個時間嘹黔,我們認(rèn)為這個消息就死了账嚎,稱之為死信。如果隊列設(shè)置了儡蔓,消息也設(shè)置了郭蕉,那么會取小的。所以一個消息如果被路由到不同的隊列中喂江,這個消息死亡的時間有可能不一樣(不同的隊列設(shè)置)召锈。這里單講單個消息的TTL,因?yàn)樗攀菍?shí)現(xiàn)延遲任務(wù)的關(guān)鍵获询。

byte[] messageBodyBytes = "Hello, world!".getBytes(); ?

AMQP.BasicProperties properties = new AMQP.BasicProperties(); ?

properties.setExpiration("60000"); ?

channel.basicPublish("my-exchange", "queue-key", properties, messageBodyBytes); ?

可以通過設(shè)置消息的expiration字段或者x-message-ttl屬性來設(shè)置時間涨岁,兩者是一樣的效果拐袜。只是expiration字段是字符串參數(shù),所以要寫個int類型的字符串:當(dāng)上面的消息扔到隊列中后梢薪,過了60秒蹬铺,如果沒有被消費(fèi),它就死了秉撇。不會被消費(fèi)者消費(fèi)到甜攀。這個消息后面的,沒有“死掉”的消息對頂上來琐馆,被消費(fèi)者消費(fèi)规阀。死信在隊列中并不會被刪除和釋放,它會被統(tǒng)計到隊列的消息數(shù)中去

處理流程圖

創(chuàng)建交換機(jī)(Exchanges)和隊列(Queues)

創(chuàng)建死信交換機(jī)


如圖所示瘦麸,就是創(chuàng)建一個普通的交換機(jī)谁撼,這里為了方便區(qū)分,把交換機(jī)的名字取為:delay

創(chuàng)建自動過期消息隊列

這個隊列的主要作用是讓消息定時過期的滋饲,比如我們需要2小時候關(guān)閉訂單厉碟,我們就需要把消息放進(jìn)這個隊列里面,把消息過期時間設(shè)置為2小時


創(chuàng)建一個一個名為delay_queue1的自動過期的隊列屠缭,當(dāng)然圖片上面的參數(shù)并不會讓消息自動過期墨榄,因?yàn)槲覀儾]有設(shè)置x-message-ttl參數(shù),如果整個隊列的消息有消息都是相同的勿她,可以設(shè)置,這里為了靈活阵翎,所以并沒有設(shè)置逢并,另外兩個參數(shù)x-dead-letter-exchange代表消息過期后,消息要進(jìn)入的交換機(jī)郭卫,這里配置的是delay砍聊,也就是死信交換機(jī),x-dead-letter-routing-key是配置消息過期后贰军,進(jìn)入死信交換機(jī)的routing-key,跟發(fā)送消息的routing-key一個道理玻蝌,根據(jù)這個key將消息放入不同的隊列

創(chuàng)建消息處理隊列

這個隊列才是真正處理消息的隊列,所有進(jìn)入這個隊列的消息都會被處理


消息隊列的名字為delay_queue2

消息隊列綁定到交換機(jī)

進(jìn)入交換機(jī)詳情頁面词疼,將創(chuàng)建的2個隊列(delayqueue1和delayqueue2)綁定到交換機(jī)上面


自動過期消息隊列的routing key 設(shè)置為delay

綁定delayqueue2


delayqueue2 的key要設(shè)置為創(chuàng)建自動過期的隊列的x-dead-letter-routing-key參數(shù)俯树,這樣當(dāng)消息過期的時候就可以自動把消息放入delay_queue2這個隊列中了

綁定后的管理頁面如下圖:


當(dāng)然這個綁定也可以使用代碼來實(shí)現(xiàn),只是為了直觀表現(xiàn)贰盗,所以本文使用的管理平臺來操作

發(fā)送消息

String msg = "hello word"; ?

MessageProperties messageProperties = newMessageProperties(); ?

? ? ? ?messageProperties.setExpiration("6000");

? ? ? ?messageProperties.setCorrelationId(UUID.randomUUID().toString().getBytes());

? ? ? ?Message message = newMessage(msg.getBytes(), messageProperties);

? ? ? ?rabbitTemplate.convertAndSend("delay", "delay",message);

設(shè)置了讓消息6秒后過期

注意:因?yàn)橐屜⒆詣舆^期许饿,所以一定不能設(shè)置delay_queue1的監(jiān)聽,不能讓這個隊列里面的消息被接受到舵盈,否則消息一旦被消費(fèi)陋率,就不存在過期了

接收消息

接收消息配置好delay_queue2的監(jiān)聽就好了

package wang.raye.rabbitmq.demo1;

import org.springframework.amqp.core.AcknowledgeMode; ?

import org.springframework.amqp.core.Binding; ?

import org.springframework.amqp.core.BindingBuilder; ?

import org.springframework.amqp.core.DirectExchange; ?

import org.springframework.amqp.core.Message; ?

import org.springframework.amqp.core.Queue; ?

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; ?

import org.springframework.amqp.rabbit.connection.ConnectionFactory; ?

import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; ?

import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; ?

import org.springframework.beans.factory.annotation.Autowired; ?

import org.springframework.context.annotation.Bean; ?

import org.springframework.context.annotation.Configuration;

@Configuration

public class DelayQueue{ ?

? ?/** 消息交換機(jī)的名字*/

? ?public static final String EXCHANGE = "delay";

? ?/** 隊列key1*/

? ?public static final String ROUTINGKEY1 = "delay";

? ?/** 隊列key2*/

? ?public static final String ROUTINGKEY2 = "delay_key";

? ?/**

? ? * 配置鏈接信息

? ? * @return

? ? */

? ?@Bean

? ?public ConnectionFactory connectionFactory() {

? ? ? ?CachingConnectionFactory connectionFactory = newCachingConnectionFactory("120.76.237.8",5672);

? ? ? ?connectionFactory.setUsername("kberp");

? ? ? ?connectionFactory.setPassword("kberp");

? ? ? ?connectionFactory.setVirtualHost("/");

? ? ? ?connectionFactory.setPublisherConfirms(true); // 必須要設(shè)置

? ? ? ?return connectionFactory;

? ?}

? ?/** ?

? ? * 配置消息交換機(jī)

? ? * 針對消費(fèi)者配置 ?

? ? ? ?FanoutExchange: 將消息分發(fā)到所有的綁定隊列球化,無routingkey的概念 ?

? ? ?? HeadersExchange :通過添加屬性key-value匹配 ?

? ? ? ?DirectExchange:按照routingkey分發(fā)到指定隊列 ?

? ? ? ?TopicExchange:多關(guān)鍵字匹配 ?

? ? */ ?

? ?@Bean ?

? ?publicDirectExchange defaultExchange() { ?

? ? ? ?returnnewDirectExchange(EXCHANGE, true, false);

? ?}

? ?/**

? ? * 配置消息隊列2

? ? * 針對消費(fèi)者配置 ?

? ? * @return

? ? */

? ?@Bean

? ?publicQueue queue() { ?

? ? ? returnnewQueue("delay_queue2", true); //隊列持久 ?

? ?}

? ?/**

? ? * 將消息隊列2與交換機(jī)綁定

? ? * 針對消費(fèi)者配置 ?

? ? * @return

? ? */

? ?@Bean ?

? ?@Autowired

? ?publicBinding binding() { ?

? ? ? ?returnBindingBuilder.bind(queue()).to(defaultExchange()).with(DelayQueue.ROUTINGKEY2); ?

? ?}

? ?/**

? ? * 接受消息的監(jiān)聽,這個監(jiān)聽會接受消息隊列1的消息

? ? * 針對消費(fèi)者配置 ?

? ? * @return

? ? */

? ?@Bean ?

? ?@Autowired

? ?publicSimpleMessageListenerContainer messageContainer2(ConnectionFactory connectionFactory) { ?

? ? ? ?SimpleMessageListenerContainer container = newSimpleMessageListenerContainer(connectionFactory()); ?

? ? ? ?container.setQueues(queue()); ?

? ? ? ?container.setExposeListenerChannel(true); ?

? ? ? ?container.setMaxConcurrentConsumers(1); ?

? ? ? ?container.setConcurrentConsumers(1); ?

? ? ? ?container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //設(shè)置確認(rèn)模式手工確認(rèn) ?

? ? ? ?container.setMessageListener(newChannelAwareMessageListener() {

? ? ? ? ? ?publicvoid onMessage(Message message, com.rabbitmq.client.Channel channel) throwsException{

? ? ? ? ? ? ? ?byte[] body = message.getBody(); ?

? ? ? ? ? ? ? ?System.out.println("delay_queue2 收到消息 : "+ newString(body)); ?

? ? ? ? ? ? ? ?channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //確認(rèn)消息成功消費(fèi) ?

? ? ? ? ? ?} ?

? ? ? ?}); ?

? ? ? ?return container; ?

? ?} ?

}

這種方式可以自定義進(jìn)入死信隊列的時間瓦糟;是不是很完美筒愚,但是有的小伙伴的情況是消息中間件就是rocketmq,公司也不可能會用商業(yè)版菩浙,怎么辦巢掺?那就進(jìn)入下一節(jié)

四、時間輪算法


(1)創(chuàng)建環(huán)形隊列芍耘,例如可以創(chuàng)建一個包含3600個slot的環(huán)形隊列(本質(zhì)是個數(shù)組)

(2)任務(wù)集合址遇,環(huán)上每一個slot是一個Set

同時,啟動一個timer斋竞,這個timer每隔1s倔约,在上述環(huán)形隊列中移動一格,有一個Current Index指針來標(biāo)識正在檢測的slot坝初。

Task結(jié)構(gòu)中有兩個很重要的屬性:

(1)Cycle-Num:當(dāng)Current Index第幾圈掃描到這個Slot時浸剩,執(zhí)行任務(wù)

(2)訂單號,要關(guān)閉的訂單號(也可以是其他信息鳄袍,比如:是一個基于某個訂單號的任務(wù))

假設(shè)當(dāng)前Current Index指向第0格绢要,例如在3610秒之后,有一個訂單需要關(guān)閉拗小,只需:

(1)計算這個訂單應(yīng)該放在哪一個slot重罪,當(dāng)我們計算的時候現(xiàn)在指向1,3610秒之后哀九,應(yīng)該是第10格剿配,所以這個Task應(yīng)該放在第10個slot的Set中

(2)計算這個Task的Cycle-Num,由于環(huán)形隊列是3600格(每秒移動一格阅束,正好1小時)呼胚,這個任務(wù)是3610秒后執(zhí)行,所以應(yīng)該繞3610/3600=1圈之后再執(zhí)行息裸,于是Cycle-Num=1

Current Index不停的移動蝇更,每秒移動到一個新slot,這個slot中對應(yīng)的Set呼盆,每個Task看Cycle-Num是不是0:

(1)如果不是0年扩,說明還需要多移動幾圈,將Cycle-Num減1

(2)如果是0宿亡,說明馬上要執(zhí)行這個關(guān)單Task了常遂,取出訂單號執(zhí)行關(guān)單(可以用單獨(dú)的線程來執(zhí)行Task),并把這個訂單信息從Set中刪除即可。

(1)無需再輪詢?nèi)坑唵慰烁欤矢?/p>

(2)一個訂單平绩,任務(wù)只執(zhí)行一次

(3)時效性好,精確到秒(控制timer移動頻率可以控制精度)

五漠另、redis過期監(jiān)聽

1.修改redis.windows.conf配置文件中notify-keyspace-events的值

默認(rèn)配置notify-keyspace-events的值為 ""

修改為 notify-keyspace-events Ex 這樣便開啟了過期事件

2. 創(chuàng)建配置類RedisListenerConfig(配置RedisMessageListenerContainer這個Bean)

package com.zjt.shop.config;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;

import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration

public class RedisListenerConfig {

? ? @Autowired

? ? private RedisTemplate redisTemplate;

? ? /**

? ? * @return

? ? */

? ? @Bean

? ? public RedisTemplate redisTemplateInit() {

? ? ? ? // key序列化

? ? ? ? redisTemplate.setKeySerializer(new StringRedisSerializer());

? ? ? ? //val實(shí)例化

? ? ? ? redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

? ? ? ? return redisTemplate;

? ? }

? ? @Bean

? ? RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

? ? ? ? RedisMessageListenerContainer container = new RedisMessageListenerContainer();

? ? ? ? container.setConnectionFactory(connectionFactory);

? ? ? ? return container;

? ? }

}

3.繼承KeyExpirationEventMessageListener創(chuàng)建redis過期事件的監(jiān)聽類

package com.zjt.shop.common.util;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;

import com.zjt.shop.modules.order.service.OrderInfoService;

import com.zjt.shop.modules.product.entity.OrderInfoEntity;

import com.zjt.shop.modules.product.mapper.OrderInfoMapper;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.connection.Message;

import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;

import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import org.springframework.stereotype.Component;

@Slf4j

@Component

public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

? ? public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {

? ? ? ? super(listenerContainer);

? ? }

? ? @Autowired

? ? private OrderInfoMapper orderInfoMapper;

? ? /**

? ? * 針對redis數(shù)據(jù)失效事件捏雌,進(jìn)行數(shù)據(jù)處理

? ? * @param message

? ? * @param pattern

? ? */

? ? @Override

? ? public void onMessage(Message message, byte[] pattern) {

? ? ? try {

? ? ? ? ? String key = message.toString();

? ? ? ? ? //從失效key中篩選代表訂單失效的key

? ? ? ? ? if (key != null && key.startsWith("order_")) {

? ? ? ? ? ? ? //截取訂單號,查詢訂單笆搓,如果是未支付狀態(tài)則為-取消訂單

? ? ? ? ? ? ? String orderNo = key.substring(6);

? ? ? ? ? ? ? QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<>();

? ? ? ? ? ? ? queryWrapper.eq("order_no",orderNo);

? ? ? ? ? ? ? OrderInfoEntity orderInfo = orderInfoMapper.selectOne(queryWrapper);

? ? ? ? ? ? ? if (orderInfo != null) {

? ? ? ? ? ? ? ? ? if (orderInfo.getOrderState() == 0) {? //待支付

? ? ? ? ? ? ? ? ? ? ? orderInfo.setOrderState(4);? ? ? ? //已取消

? ? ? ? ? ? ? ? ? ? ? orderInfoMapper.updateById(orderInfo);

? ? ? ? ? ? ? ? ? ? ? log.info("訂單號為【" + orderNo + "】超時未支付-自動修改為已取消狀態(tài)");

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? }

? ? ? ? ? }

? ? ? } catch (Exception e) {

? ? ? ? ? e.printStackTrace();

? ? ? ? ? log.error("【修改支付訂單過期狀態(tài)異承允】:" + e.getMessage());

? ? ? }

? ? }

}

4:測試

通過redis客戶端存一個有效時間為3s的訂單:


結(jié)果:


轉(zhuǎn)載自:https://mp.weixin.qq.com/s/1AZTGyAECYfhW5LZ44sF3A

作者:阿牛

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者满败。
  • 序言:七十年代末肤频,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子算墨,更是在濱河造成了極大的恐慌宵荒,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件净嘀,死亡現(xiàn)場離奇詭異报咳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挖藏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門暑刃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人膜眠,你說我怎么就攤上這事岩臣。” “怎么了宵膨?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵婿脸,是天一觀的道長。 經(jīng)常有香客問我柄驻,道長,這世上最難降的妖魔是什么焙压? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任鸿脓,我火速辦了婚禮,結(jié)果婚禮上涯曲,老公的妹妹穿的比我還像新娘野哭。我一直安慰自己,他們只是感情好幻件,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布拨黔。 她就那樣靜靜地躺著,像睡著了一般绰沥。 火紅的嫁衣襯著肌膚如雪篱蝇。 梳的紋絲不亂的頭發(fā)上贺待,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音零截,去河邊找鬼麸塞。 笑死,一個胖子當(dāng)著我的面吹牛涧衙,可吹牛的內(nèi)容都是我干的哪工。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼弧哎,長吁一口氣:“原來是場噩夢啊……” “哼雁比!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撤嫩,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤偎捎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后非洲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸭限,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年两踏,在試婚紗的時候發(fā)現(xiàn)自己被綠了败京。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡梦染,死狀恐怖赡麦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帕识,我是刑警寧澤泛粹,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站肮疗,受9級特大地震影響晶姊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伪货,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一们衙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碱呼,春花似錦蒙挑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春馋袜,著一層夾襖步出監(jiān)牢的瞬間男旗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工桃焕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剑肯,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓观堂,卻偏偏與公主長得像让网,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子师痕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理溃睹,服務(wù)發(fā)現(xiàn),斷路器胰坟,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 為了一些初學(xué)習(xí)者更好理解我就從簡單的解釋一下Rabbitmq的原理吧?因篇,首先你可以這樣想RabbitMq就是一個隊...
    螃蟹和駱駝先生Yvan閱讀 7,403評論 6 4
  • 個人專題目錄 1. RabbitMQ 高級特性 1.1 消息可靠性投遞 在使用 RabbitMQ 的時候,作為消息...
    Java及SpringBoot閱讀 510評論 0 3
  • 場景 開發(fā)中經(jīng)常需要用到定時任務(wù)笔横,對于商城來說竞滓,定時任務(wù)尤其多,比如優(yōu)惠券定時過期吹缔、訂單定時關(guān)閉商佑、微信支付2小時未...
    Raye閱讀 1,273評論 0 13
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭厢塘,有人歡樂有人憂愁茶没,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,535評論 28 53