消息队列MQ的三大功能

一、消息队列MQ的三大功能

1.异步处理

2.应用解耦:A系统的代码需要调用B系统的接口才能完成一个任务,假设B两个系统崩溃了,那么A系统就会出现超时,导致整个系统不可用。如果使用消息队列可以将A调用B接口的参数放到队列里面,A代码里面不再直接去调用B系统的接口,而是让B系统订阅消费A发送过来的的消息(参数),B系统根据这些消息(参数)修改自己的逻辑即可,B系统维护时,不会影响A系统的使用。

3.流量控制:系统最高能承受100万并发,超出了就用队列进行排队。

二、高级消息队列交换机模型:

      1. P2P(点对点)-----     direct exchange    路由键精确匹配连接队列

      2.pub/sub (发布/订阅)  以路由键匹配规则不同,划分出不同类型

              |-----  fanout exchange  没有路由键概念,扇出交换机直连绑定的队列

              |------  topic exchange  路由键由.分割,模糊匹配连接队列* 匹配一个单词,#匹配0个或多个单词

              |------  headers exchange

              |------  system exchange

补充:

消息可靠投递:要消息要不丢失,保证三个阶段不出现问题,如图:

方案1:结合Rabbitmq 确认回调和回退回调处理

   <<<<<<<<注意:发送消息前要将消息保存到Mysql起来>>>>>>>>

package com.atguigu.rabbitmq.springbootrabbitmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;

/**
 * User: ldj
 * Date: 2023/3/13
 * Time: 20:19
 * Description: No Description
 */
@Slf4j
@Component
public class RabbitmqCallbackConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    //第二种写法(推荐) 
    @PostConstruct
    public void initRabbitTemplate() {

        rabbitTemplate.setConfirmCallback((@Nullable CorrelationData correlationData, boolean ack, @Nullable String cause) -> {

            if (!ack) {
                //TODO 消息没成功抵达Broker,修改Mysql消息状态标识,并从缓存获取再次发送
            }
        });


        rabbitTemplate.setReturnCallback((Message message, int replyCode, String replyText, String exchange, String routingKey) -> {

            if (!StringUtils.isEmpty(replyText)) {
                //TODO 消息没成功抵达Queue,修改Mysql消息状态标识,弄一个定时任务读取数据mysql并向Rabbitmq发送消息
            }
        });
    }

}

方案2:备用交换机也能很好解决第二阶段问题

 三、docker 安装rabbitmq

docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management

#5671、5672 (AMQP端口)
#15672(web可视管理后台端口)
#4369、25672(Erlang发现&集群端口)
#61613、61614(STOMP协议端口)
#1883、8883(MQTT协议端口)


#开机自启
docker update rabbitmq --restart=always

四、springBoot整合rabbitmq 以及测试

#1.引入rabbitmq启动依赖

        <!--rabbitMQ-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>


#2.简单配置文件application.properties,必须以spring.rabbitmq开头

     spring.rabbitmq.host=192.168.xxx.xxx
     spring.rabbitmq.virtual-host=/
     spring.rabbitmq.port=5672
     #默认账号密码
     spring.rabbitmq.username=guest
     spring.rabbitmq.password=guest


#3.启动类加上@EnableRabbit

 配置文件 application.properties

#rabbitmq安装在虚拟机的地址
spring.rabbitmq.host=192.168.126.131
#rabbitmq又进行虚拟服务器划分,默认是/
spring.rabbitmq.virtual-host=/
#默认端口
spring.rabbitmq.port=5672
#默认账号密码
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

#开启消息确认回调(PB) 
spring.rabbitmq.publisher-confirm-type=correlated
#确认消息传达队列
spring.rabbitmq.publisher-returns=true
#异步发送优先回调返回确认
spring.rabbitmq.template.mandatory=true
#手动应答ack,自动模式宕机会导致消息丢失
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#限制消费者每秒从队列拉取的消息的数量
spring.rabbitmq.listener.simple.prefetch=1000

 1.组件准备

package com.atguigu.rabbitmq.springbootrabbitmq.config;

import com.atguigu.rabbitmq.springbootrabbitmq.config.properties.RabbitmqProperties;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

/**
 * User: ldj
 * Date: 2023/3/13
 * Time: 17:56
 * Description: No Description
 */
@Configuration
public class PublishConfirmConfig {

    public static final String CONFIRM_EXCHANGE_NAME = "CF-EXC";
    public static final String BACKUP_EXCHANGE_NAME = "BU-EXC";

    public static final String CONFIRM_QUEUE_NAME = "CF-QUE";
    public static final String BACKUP_QUEUE_NAME = "BU-QUE";
    public static final String WARNING_QUEUE_NAME = "WAR-QUE";

    /*
     *  P ----> CF-EXC --(b)--> CF-QUE ----> confirmConsumer                            |
     *            |                               ^
     *            v                               |
     *          BU-EXC ----> BU-QUE----------------
     *            |
     *            v
     *          WAR-QUE -------------------> warningConsumer
     */

    /**
     * 声明队列
     */
    @Bean("confirmQueue")
    public Queue confirmQueue() {
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    @Bean("backupQueue")
    public Queue backupQueue() {
        return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
    }

    @Bean("warningQueue")
    public Queue warningQueue() {
        return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
    }

    /**
     * 声明交换机
     */
    @Bean("confirmExchange")
    public TopicExchange confirmExchange() {
        HashMap<String, Object> arguments = new HashMap<>();

        return ExchangeBuilder
                .topicExchange(CONFIRM_EXCHANGE_NAME)
                .durable(true)
                .alternate(BACKUP_EXCHANGE_NAME) //备份交换机
                .withArguments(arguments)
                .build();
    }

    @Bean("backupExchange")
    public FanoutExchange backupExchange() {
        return new FanoutExchange(BACKUP_EXCHANGE_NAME, true, false, new HashMap<>());
    }

    /**
     * 交换机与队列捆绑
     */
    @Bean
    public Binding confirmQueueToConfirmExchange(@Qualifier("confirmQueue") Queue confirmQueue,
                                                 @Qualifier("confirmExchange") TopicExchange confirmExchange,
                                                 RabbitmqProperties properties) {
        return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(properties.getConfirmBingingKey());
    }

    @Bean
    public Binding backupQueueToBackupExchange(@Qualifier("backupQueue") Queue confirmQueue,
                                               @Qualifier("backupExchange") FanoutExchange confirmExchange) {
        return BindingBuilder.bind(confirmQueue).to(confirmExchange);
    }

    @Bean
    public Binding warningQueueToBackupExchange(@Qualifier("warningQueue") Queue confirmQueue,
                                                @Qualifier("backupExchange") FanoutExchange confirmExchange) {
        return BindingBuilder.bind(confirmQueue).to(confirmExchange);
    }

}

 2.消费者1

package com.atguigu.rabbitmq.springbootrabbitmq.listener;

import com.atguigu.rabbitmq.springbootrabbitmq.config.PublishConfirmConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * User: ldj
 * Date: 2023/3/13
 * Time: 18:45
 * Description: No Description
 */
@Slf4j
@Component
@RabbitListener(queues = {PublishConfirmConfig.CONFIRM_QUEUE_NAME,PublishConfirmConfig.BACKUP_QUEUE_NAME})
public class ConfirmListener {

    @RabbitHandler
    public void receiveConfirmMsg(Message message, Channel channel, String msg) {

        log.info("接收到队列CF-QUE/BU-QUE消息:{}", msg);
    }
}

消费者2

package com.atguigu.rabbitmq.springbootrabbitmq.listener;

import com.atguigu.rabbitmq.springbootrabbitmq.config.PublishConfirmConfig;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.net.InetAddress;

/**
 * User: ldj
 * Date: 2023/3/14
 * Time: 8:32
 * Description: No Description
 */
@Slf4j
@Component
@RabbitListener(queues = {PublishConfirmConfig.WARNING_QUEUE_NAME})
public class WarningListener {
    @RabbitHandler
    public void receiveConfirmMsg(Message message, Channel channel, String msg) {
        Connection connection = channel.getConnection();
        InetAddress address = connection.getAddress();
        String hostAddress = address.getHostAddress();

        log.warn("警告! [CF-EXC <{}>] 收到不可路由消息,已转发给备份交换机[BU-EXC]:{}", hostAddress, msg);
    }
}

 3.生产者

package com.atguigu.rabbitmq.springbootrabbitmq.controller;

import com.atguigu.rabbitmq.springbootrabbitmq.config.PublishConfirmConfig;
import com.atguigu.rabbitmq.springbootrabbitmq.config.properties.RabbitmqProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * User: ldj
 * Date: 2023/3/13
 * Time: 18:26
 * Description: No Description
 */
@Slf4j
@RestController
@RequestMapping("/rabbitmq/confirm")
public class ConfirmMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // http://localhost:8089/rabbitmq/confirm/sendMsg/发布确认可靠消息

    @GetMapping(value = "/sendMsg/{message}")
    public void sendConfirmMsg(@PathVariable String message) {
        log.info("[sendConfirmMsg]要开始发消息:{}", message);
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setAppId("666888");

        Message msg = new Message(message.getBytes(StandardCharsets.UTF_8), messageProperties);

        //封装数据给确认回调接口
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId(UUID.randomUUID().toString().replace("-", ""));
        correlationData.setReturnedMessage(msg);
        rabbitTemplate.convertAndSend(PublishConfirmConfig.CONFIRM_EXCHANGE_NAME, RabbitmqProperties.confirmRoutingKey + 1, message, correlationData);
    }
}
package com.atguigu.rabbitmq.springbootrabbitmq.config.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * User: ldj
 * Date: 2023/3/12
 * Time: 18:17
 * Description: No Description
 */
@Data
@Component
@ConfigurationProperties("spring.rabbitmq.properties")
public class RabbitmqProperties {

    private String normal1BingingKey;

    private String normal2BingingKey;

    private String normal3BingingKey;

    private String delayedBingingKey;

    private String deadLetterBingingKey;

    private String confirmBingingKey;

    private Integer normal1QueueTtl = 10000;

    private Integer normal2QueueTtl = 30000;


    public static String confirmRoutingKey;

    public void setConfirmRoutingKey(String confirmRoutingKey) {
        RabbitmqProperties.confirmRoutingKey = confirmRoutingKey;
    }
    
}

 测试发现消息是对象时接收到消息不是JSON串,属性是Date类型也没有格式化,所以配上消息转换配置             

package com.atguli.common.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.text.SimpleDateFormat;

/**
 * User: ldj
 * Date: 2022/10/10
 * Time: 17:07
 * Description: rabbitmq序列化配置
 */

@Configuration
public class MyRabbitConfig {

    @Bean
    public MessageConverter messageConverter() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        //Long -> String 解决返回前端id是Long类型精度降低,后位数变成0
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);

        //Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,忽略该字段
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        // 允许出现单引号
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

        return new Jackson2JsonMessageConverter(objectMapper);
    }
}

         

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值