资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Java

JavaWEB项目中如何实现延迟消息(延迟处理)

Java 更新时间: 发布时间: 计算机考试归档 最新发布

JavaWEB项目中如何实现延迟消息(延迟处理)

现在有一个场景:商品下订单成功后,需要在30分钟进行支付,若超过30分钟尚未支付则认为订单超时作废此订单。请你设计一个方案实现此功能。

首先这个问题是一个好问题,它里面涉及到了两个最常用的开发概念:PUSH和PULL。

解释一下

PUSH:外部进行信息推送,本地被动接受消息,进行数据处理。

PULL:本地进行数据的主动获取,通过接口,协议等方式,主动去获取是否有数据要进行处理。

对于上述的问题,那么久可以选取这两种方案来进行解决,可以比较一下实现的复杂度和执行效率。

PULL方案

设置一个定时运行的程序,每间隔XX秒(分钟),执行一次数据库查询数据库中数据超过30分钟尚未付款订单,进行更新订单状态为作废状态。

这个方案存在问题:你的定时任务的时间间隔设置为多少呢?60S?三分钟?订单超时是一个瞬间的状态,这样会出现一种情况,就是订单已经超时,但是,状态尚未改变,因为它需要定时任务执行一次,才能改变订单的状态。这样在业务上存在天然的缺陷。有人会说,我执行的频率改为1S一次,就解决了这个时间的问题。那么系统会不会添加很多的无效的扫描呢?数据库压力会不会增大呢?

PUSH方案(异步通知方案)

当系统添加一个订单的时候,向某个地方存储一个订单ID的信息,此信息可以设置定时的时间。本地系统存在一个监听器,当消息推送到当前系统时,根据订单ID,去检查订单是否已经支付,然后订单修改状态。

这样的异步通知可以使用下面两种方案:

1.RabbitMQ的延迟消息(死信队列&延迟队列)

2.使用redis的setex

其实还有一种方案可以实现此需求,那就是使用quartz定时调度。

当订单生成时候,在Quartz中生成一个30分钟的定时调度,将订单ID作为参数传递,等30分钟后,quartz调度此任务来执行检查。但是此方案有很大弊端,因为你的任务存储在内存中,假如系统任务数过多,会大量消耗系统的资源。

---------------------------------------------具体实现-----------------------------------------------------------------

1.RabbitMQ的延迟消息

rabbitMQ的延迟消息可以使用两种方式来做.延迟队列和死信队列,因为延迟队列需要安装插件(源码又在github上,很难搞到合适的插件,我有一份3.8.0的版本的插件,有需要的可以咨询),所以,当前方案使用死信队列来做延迟消息的处理。

环境:spring boot2.x,rabbitmq 3.8.4(erlang 23.X)

a.设置rabbitmq绑定关系的config配置

    @Bean("oriUseQueue")
    public Queue queue() {
        Map map = new HashMap();
        map.put("x-message-ttl", 10000); // 10秒钟后成为死信
        map.put("x-dead-letter-exchange", "SYS_DEAD_LETTER_EXCHANGE"); // 队列中的消息变成死信后,进入死信交换机
        return new Queue("ORI_USE_QUEUE", true, false, false, map);
    }

    
    @Bean("oriUseExchange")
    public DirectExchange oriUseExchange() {
        return new DirectExchange("ORI_USE_EXCHANGE", true, false, new HashMap<>());
    }

    
    @Bean
    public Binding binding(@Qualifier("oriUseQueue") Queue queue,@Qualifier("oriUseExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("ori.use");
    }

    
    @Bean("deatLetterExchange")
    public TopicExchange deadLetterExchange() {
        return new TopicExchange("SYS_DEAD_LETTER_EXCHANGE", true, false, new HashMap<>());
    }

    
    @Bean("deatLetterQueue")
    public Queue deadLetterQueue() {
        return new Queue("SYS_DEAD_LETTER_QUEUE", true, false, false, new HashMap<>());
    }

    
    @Bean
    public Binding bindingDead(@Qualifier("deatLetterQueue") Queue queue,@Qualifier("deatLetterExchange") TopicExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("#"); // 无条件路由
    }

b.代码发送延迟消息

    @Autowired
    AmqpTemplate amqpTemplate;
    
    public void lazyMessageSend() {
        long now = System.currentTimeMillis();
        amqpTemplate.convertAndSend("ORI_USE_EXCHANGE", "ori.use", "-------- a direct msg,prepare to dead msg!,timestamp is:" + now);
    }

c.设置监听死信队列

@Component
@RabbitListener(queues = "SYS_DEAD_LETTER_QUEUE")
public class DeadLetterMessageReceiver {

    @RabbitHandler
    public void process(String msg) {
        long now = System.currentTimeMillis();
        
        System.out.println("SYS_DEAD_LETTER_QUEUE 死信队列收到消息了!!!内容:" + msg);
        System.out.println("当前时间:" + now);
    }
}

死信队列能发送延迟消息的图示:

 

2.使用redis的setex来实现延迟通知

redis是可以通过修改配置文件中配置信息来实现过期消息的通知。订单创建完毕之后,就可以在redis存储一个订单ID的key,设置这个ID的过期时间为30分钟,这样30分钟后,键过期后,就可以在监听程序中收到redis的通知!

a.修改redis配置

修改notify-keyspace-events的值为“EX”:

notify-keyspace-events "Ex"

修改完成后,需要重启redis服务。

b.在自己的spring_boot项目中配置监听容器:

   @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }

c.编写监听程序:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class KeyExpireListener extends KeyExpirationEventMessageListener {
    @Autowired
    private StringRedisTemplate redisTemplate;
    public KeyExpireListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String expiredKey = message.toString();
        System.out.println("message:" + message);
        log.info("expiredKey=========" + expiredKey);
        //获取过期key存储的数据
        Object value = redisTemplate.opsForHash().get("value",
                expiredKey);
        log.info(value.toString());
        //  todo      process programming
        //删除过期key存储的数据
        redisTemplate.opsForHash().delete("value", expiredKey);
    }
}

这样就可以在WEB系统中收到延迟消息了。

转载请注明:文章转载自 http://www.konglu.com/
本文地址:http://www.konglu.com/it/861059.html
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【JavaWEB项目中如何实现延迟消息(延迟处理)】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2