厚积薄发打卡Day92: MongoDB + RabbitMQ 构建消息推送功能(下)<RabbitMQ简易入门与整合>

产品选用

消息队列产品有很多,比如说常见的有Kafka、RocketMQ、RabbitMQ和ActiveMQ:

  • 其中了解后Kafka的性能是最好的,并发量比较大,而且消息收发的速度也非常快。但是消息收发的可靠性上,Kafka不如RabbitMQ,而且技术选型的时候执行速度并不是唯一标准,开发效率、易用性也是需要考虑的。
  • RabbitMQ具有支持消息异步收发,又支持同步收发的特点,虽然现在大部分的场景对应的是消息异步收发,但是有的场合要支持消息的同步收发,这时候RabbitMQ能适应各种业务场景的优点就显现出来了。所以在顾及程序健壮性时,选用了RabbitMQ。

RabbitMQ在系统中的作用(削峰填谷

  • 通过把消息发送到消息队列上,等用户登录时再把信息获从消息队列中取出,存入mongoDB中实现

RabbitMQ快速入门

  1. 安装,通过docker即可快速安装

    1. 拉取镜像:

      docker pull rabbitmq:3.7.7-management
      
    2. 启动:

      根据下载的镜像创建和启动容器

      docker run -d --name rabbitmq3.7.7 -p 5672:5672 -p 15672:15672 -v `pwd`/data:/var/lib/rabbitmq --hostname myRabbit -e RABBITMQ_DEFAULT_VHOST=my_vhost  -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin df80af9ca0c9
      

      说明:

      • -d 后台运行容器;
      • –name 指定容器名;
      • -p 指定服务运行的端口(5672:应用访问端口;15672:控制台Web端口号);
      • -v 映射目录或文件;
      • –hostname 主机名(RabbitMQ的一个重要注意事项是它根据所谓的 “节点名称” 存储数据,默认为主机名);
      • -e 指定环境变量;(RABBITMQ_DEFAULT_VHOST:默认虚拟机名;RABBITMQ_DEFAULT_USER:默认的用户名;RABBITMQ_DEFAULT_PASS:默认用户名的密码)
    3. 查看,启动成功:

      在这里插入图片描述

  2. 五种队列模式

    具体例子:RabbitMQ 详解 五种队列-SpiritMark_liu - 云+社区 - 腾讯云 (tencent.com)

    在此次消息模块的功能使用(topic)主题模式实现

    1. 简单模式

      一个生产者(发送方)对应一个消费者(接收方)

      在这里插入图片描述

    2. Work模式

      一个生产者对应多个消费者,但是只能有一个消费者获得消息(排他)

      在这里插入图片描述

    3. 发布/订阅模式

      一个消费者将消息首先发送到fanout交换器,交换器绑定到多个队列,然后与之对应的所有消费者都能接收到消息(不排他)

      在这里插入图片描述

    4. 路由模式

    生产者将消息发送到direct交换器,交换器按照关键字(Key),把消息路由到某个队列

    在这里插入图片描述

    1. 主题模式(√)

      生产者将消息发送到Topic交换器,交换器按照复杂的规则,把消息路由到某个队列

      在这里插入图片描述

  3. 消息持久化

    消息的可靠性是RabbitMQ的一大特色,那么RabbitMQ是如何保证消息可靠性的呢?答案就是消息持久化。持久化可以防止在异常情况下丢失数据。除了消息持久化之外,甚至交换器和队列都能持久化。

  4. 消息过期时间

    默认情况下,消息是无限期存储在RabbitMQ上面的,但是我们可以设置消息过期时间,到期之后无论该消息是否已经被接收,都会被RabbitMQ删除。

  5. Ack应答

    消费者接收消息之后,必须返回一个Ack应答,那么RabbitMQ才会认为这条消息接收成功。如果想要删除这条消息,消费者发送Ack应答的时候,附带一个deliveryTag标志位就可以了。

功能整合

  1. pom.xml文件中添加RabbitMQ的依赖库

    <dependency>
    	<groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.9.0</version>
    </dependency>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  2. 使用我们采用【 异步线程同步收发消息】实现消息推送功能:

    1. 线程执行分为同步和异步,MQ消息收发的API也分为异步和同步;现在是线程异步执行,调用MQ同步收发API
    2. MQ的异步准确的说是阻塞式的收发消息,没收到消息也不退出,一直在后端运行处于阻塞状态,一直等待有新消息的到来。类比Servlet,它也是阻塞执行的。如果我们采用了异步收发消息,迟迟不退出线程,那么线程就没办法回收到线程池,所以线程池很快被耗光。所以在系统中采用的是RabbitMQ自带的同步收发消息方式。
    3. 使用MQ同步执行,收完消息就退出,没有消息,接收程序也不退出,
  3. 异步收发配置yaml文件,同步收发则需配置ConnectionFactory对象:

    @Configuration
    public class RabbitMQConfig {
        @Bean
        public ConnectionFactory getFactory() {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("***"); //Linux主机的IP地址
            factory.setPort(5672); //RabbitMQ端口号
            return factory;
        }
    }
    
  4. 创建线程任务类:

    @Slf4j
    @Component
    public class MessageTask {
    
        @Autowired
        private ConnectionFactory factory;
    
        @Autowired
        private MessageService messageService;
    
        /**
         * 同步发送消息
         *
         * @param topic
         * @param entity
         */
        public void send(String topic, MessageEntity entity) {
            //向MongoDb保存消息数据,返回消息主键
            String id = messageService.insertMessage(entity);
            //向rabbitMQ发送消息
            try {
                //类比jdbc:创建连接,创建statement,执行sql
                Connection connection = factory.newConnection();
                Channel channel = connection.createChannel();
                //连接到某个Topic
                channel.queueDeclare(topic, true, false, false, null);
                //存放属性数据:
                HashMap header = new HashMap();
                header.put("messageId", id);
                //创建AMQP协议参数对象,添加附加属性
                AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().headers(header).build();
                channel.basicPublish("", topic, properties, entity.getMsg().getBytes());
                log.debug("消息发送成功");
            } catch (Exception e) {
                log.error("执行异常", e);
                throw new EmosException("向MQ发送消息失败");
            }
        }
    
    
        /**
         * 异步发送消息
         *
         * @param topic
         * @param entity
         */
        @Async
        //messageTask.sendAsync(userid + "", entity);
        public void sendAsync(String topic, MessageEntity entity) {
            send(topic, entity);
        }
    
    
        /**
         * 同步接收数据
         *
         * @param topic
         * @return
         */
        public int receive(String topic) {
            int i = 0;
            try {
                //创建mq连接
                Connection connection = factory.newConnection();
                Channel channel = connection.createChannel();
                //从队列中获取消息,不自动确认
                channel.queueDeclare(topic, true, false, false, null);
                //Topic中有多少条数据位置,所以使用死循环接收数据,直到接收不到数据,退出死循环
                while (true) {
                    //创建响应接收数据,禁止自动发送ack应答
                    GetResponse response = channel.basicGet(topic, false);
                    if (response != null) {
                        AMQP.BasicProperties properties = response.getProps();
                        Map<String, Object> headers = properties.getHeaders();
                        String messageId = (String) headers.get("messageId");
                        byte[] body = response.getBody();
                        String message = new String(body);
                        log.debug("从RabbitMQ接收的消息" + message);
    
                        MessageRefEntity entity = new MessageRefEntity();
                        entity.setMessageId(messageId);
                        entity.setReceiverId(Integer.parseInt(topic));
                        entity.setReadFlag(false);
                        entity.setLastFlag(true);
                        //把消息存储在MongoDB中
                        messageService.insertRef(entity);
                        //数据保存到MongoDB后,才发送给ack应答 让topic删除这条消息
                        long deliveryTag = response.getEnvelope().getDeliveryTag();
                        channel.basicAck(deliveryTag, false);
                        i++;
                    } else {
                        break;//接收不到消息则退出循环
                    }
                }
            } catch (Exception e) {
                log.error("执行异常", e);
                throw new EmosException("获取消息出现异常");
            }
    
            return i;
        }
    
        /**
         * 异步接收数据
         *
         * @param topic
         * @return
         */
        @Async
        //异步接收消息
        //messageTask.receiveAysnc(userId + "");
        public int receiveAysnc(String topic) {
            return receive(topic);
        }
    
        /**
         * 同步删除消息队列
         *
         * @param topic 主题
         */
        public void deleteQueue(String topic) {
            try (Connection connection = factory.newConnection();
                 Channel channel = connection.createChannel()) {
                channel.queueDelete(topic);
                log.debug("消息队列成功删除");
            } catch (Exception e) {
                log.error("删除队列失败", e);
                throw new EmosException("删除队列失败");
            }
        }
    
        /**
         * 异步删除消息队列
         *
         * @param topic 主题
         */
        @Async
        public void deleteQueueAsync(String topic) {
            deleteQueue(topic);
        }
    }
            throw new EmosException("删除队列失败");
            }
        }
    
        /**
         * 异步删除消息队列
         *
         * @param topic 主题
         */
        @Async
        public void deleteQueueAsync(String topic) {
            deleteQueue(topic);
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值