RabbitMQ笔记


前言

面向消息的中间件(message-oriented middleware0) MOM能够很好的解决以上的问题。 是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。 通过提供消息传递和消息排队模型在分布式环境下提供应用解耦,弹性伸缩,冗余存储,流量削峰,异步通信,数据同步等

一、消息发送流程

发送者把消息发给消息服务器,消息服务器把消息存放在若干队列/主题中,在合适的时候,消息服务器会把消息转发给接受者。在这个过程中,发送和接受是异步的,也就是发送无需等待,发送者和接受者的生命周期也没有必然关系在发布pub/订阅sub模式下,也可以完成一对多的通信,可以让一个消息有多个接受者

Rabbitmq的特点

  • 可靠性(Reliability)(消息丢失率 (千万分之1):使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
  • 灵活的路由(交换机)
  • 消息集群:多个RabbitMQ服务器组成一个集群,形成一个逻辑Broker
  • 高可用(Highly Available Queues)
  • 多种协议(Multi-protocol):RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。
  • 多语言客户端(Many Clients)

二、RabbitMQ安装

Rabbitmq->Erlang -> 安装Erlang 虚拟机->跑Rabbitmq 比较麻烦
Rabbitmq 对docker的支持非常到位!官网经常更新镜像,所以怎么办呢。废话,用docker跑啊

1.docker命令安装

15672:图形化界面的端口
5672 :客户端数据的端口

docker run --name rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=123456 -d rabbitmq:3-management

rabbitmq的基本图解
在这里插入图片描述

关于客户端上设置v-host 啥的一堆东西太多了,我就不截图了╮( ̄▽  ̄)

2.RabbitMQ支持的消息模型

https://www.rabbitmq.com/getstarted.html

这几张图时最重要的啦
在这里插入图片描述
在这里插入图片描述


spring中RabbitMQ的使用

hello模式
在这里插入图片描述

  • hello模式消息的发送
/**
     * hello模式消息的发送
     * 并没有使用boot的依赖 所以我们需要创建连接等操作
     *
     * @throws Exception
     */
    @Test
    public void sendMessage()throws Exception{
        //1.创建一个连接工厂方便我们连接MQ
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接参数
        connectionFactory.setHost("8.136.119.31");
        //设置连接mq的端口
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("ming");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/v-ming");
        //3.从连接工厂创建一个连接
        Connection connection = connectionFactory.newConnection();
        //4.得到一个信道
        Channel channel = connection.createChannel();
        //5.声明一个队列 创建队列
        //第一个参数 对列名称
        //第二个参数 这个队列是否持久化 下次重启 消息还在
        //第三个参数 该队列是否被这个信道独占
        //第四个参数 自动删除 当队列里面没有消息了 这个队列就被删掉
        //第五个参数 可以设置一些其他参数 例如延迟队列的参数
        channel.queueDeclare("hello",true,false,false,null);
        //6.生产一个消息
        //第一个参数 设置交换机
        //第二个参数 路由Key 他和队列共用参数
        //三个参数 属性设置
        //第四个参数 消息体
        channel.basicPublish("","hello",null,"我是hello模式的第一个消息".getBytes());
        //7.关闭通道
        channel.close();
        //8.关闭连接
        connection.close();
        System.out.println("消息发送成功");
    }

hello模式的消息接收

 /**
     * hello模式的消费
     *
     * @throws Exception
     */
    @Test
    public void testHelloConsumer() throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("8.136.119.31");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("ming");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/v-ming");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //要指定消费哪一个队列里面的消息
        channel.queueDeclare("hello",true,false,false,null);
        //消费消息了 从通道里面消费消息
        //第一个参数 队列名称 指定要消费那个队列里面的消息
        //第二个参数 是否自动签收
        //回调函数 处理消息的方法
        channel.basicConsume("hello",true,new DefaultConsumer(channel){
            //重写一个回调函数
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(consumerTag);
                System.out.println(envelope);
                System.out.println(properties);
                System.out.println(new String(body));
            }
        });
        //挂起jvm一定要放在关闭通道之前
        System.in.read();
        channel.close();
        connection.close();
        System.out.println("消费成功");
    }

(注意有几个消息队列就new 几个queue)

  • 后面的模式大同小异,我先弄个工具类
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.springframework.util.ObjectUtils;


/**
 * @Author:ming
 * @Desc:mq的连接工具类
 */
public class RabbitMqUtil {

    private static ConnectionFactory connectionFactory;

    /**
     * 类加载的时候执行
     */
    static {
        connectionFactory = new ConnectionFactory();
        //2.设置一些连接参数
        connectionFactory.setHost("8.136.119.31");
        //设置连接MQ的端口
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("ming");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/v-ming");
    }

    /**
     * 获取连接的方法
     * @return
     */
    public static Connection getConnection(){
        Connection connection = null;
        try {
            connection = connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }

    /**
     * 关闭连接
     * @param channel
     * @param connection
     */
    public static void closeConnAndChannel(Channel channel,Connection connection){
        try {
            if(!ObjectUtils.isEmpty(channel)){
                channel.close();
            }
            if (!ObjectUtils.isEmpty(connection)){
                connection.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 第三种模型 (fanout)
/**
     * 测试广播模式的生产者
     * 简易的微信公众号 可以使用这个模式
     * 当生产者发送一个消息后 与交换机绑定的队列都可以收到消息 广播的感受感觉
     * @throws Exception
     */
    @Test
    public void testFanoutProducer() throws Exception{
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        //生产者不需要关心对列表 只需要往交换机里面发送消息
        //声明一个交换机 交换机有很多类型
        channel.exchangeDeclare("fanoutEx", BuiltinExchangeType.FANOUT);
        //开始发消息
        channel.basicPublish("fanoutEx","",null,"我是一个广播类型的消息".getBytes());
        RabbitMqUtil.closeConnAndChannel(channel,connection);
        System.out.println("广播消息发送成功");
    }

/**
     * 广播消息的消费者1
     *
     * @throws Exception
     */
    @Test
    public void testFanoutConsumer1() throws Exception{
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        //我们医药一个队列 和交换机绑定
        channel.exchangeDeclare("fanoutEx",BuiltinExchangeType.FANOUT);
        //获取一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //和交换机绑定一下
        channel.queueBind(queue,"fanoutEx","");
        //消费消息
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("我是第一个消费者,我正在消费广播消息" + new String(body));
            }
        });
        System.in.read();
        RabbitMqUtil.closeConnAndChannel(channel,connection);
    }

    /**
     * 广播消息的消费者2
     *
     * @throws Exception
     */
    @Test
    public void testFanoutConsumer2() throws Exception{
        Connection connection = RabbitMqUtil.getConnection();
        Channel channel = connection.createChannel();
        //我们医药一个队列 和交换机绑定
        channel.exchangeDeclare("fanoutEx",BuiltinExchangeType.FANOUT);
        //获取一个临时队列
        String queue = channel.queueDeclare().getQueue();
        //和交换机绑定一下
        channel.queueBind(queue,"fanoutEx","");
        //消费消息
        channel.basicConsume(queue,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("我是第二个消费者,我正在消费广播消息" + new String(body));
            }
        });
        System.in.read();
        RabbitMqUtil.closeConnAndChannel(channel,connection);
    }

三、Springboot方式的RabbitMQ

1. 导入依赖 配置yml文件

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-amqp</artifactId>
	</dependency>
  • 我用的是分布式的项目(生产者、消费者都有配yml文件)
#rabbitmq生产者配置
server:
  port: 8081
spring:
  application:
    name: producer
  rabbitmq:
    host: 8.136.119.31 #云服务器
    port: 5672
    username: ming
    password: 123456
    virtual-host: /v-ming #虚拟主机
    publisher-confirm-type: correlated #必须配置这个才会确认回调
    publisher-returns: true 
#rabbitmq配置
server:
  port: 8082
spring:
  application:
    name: producer
  rabbitmq:
    host: 8.136.119.31
    port: 5672
    username: ming
    password: 123456
    virtual-host: /v-ming #虚拟主机
    listener: #监听在消费者里面
      direct:
        acknowledge-mode: manual #手动签收
      simple:
        acknowledge-mode: manual

2. config配置

  • 生产者
package com.ming.config;

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 javax.annotation.PostConstruct;

/**
 * @Author:ming
 * @Desc: 消息状态的改变
 */
public class MyWatchConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 我们现在需要 在RabbitTemplate 初始化以后设置回调
     *
     * @PostConstruct 这个注解 spring遵循了这个注解的规范
     * 在bean对象创建完以后 spring会执行这个注解的方法 帮我们后置增强我们的bean对象
     * 这个注解执行在做无参无返回值的方法上
     */
    @PostConstruct
    public void setMqCall() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }


    /**
     * 消息到达交换机的回调
     *
     * @param correlationData
     * @param ack
     * @param cause
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        System.out.println(ack ? "消息到达交换机" : "消息未到达交换机");
    }

    /**
     * 消息未到达队列的回调
     *
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        // mysql 的status改成2
        System.out.println("消息未到达队列的回到");
        String s = new String(message.getBody());
        System.out.println(s);
    }
}
  • 消费者配置 (队列与交换机以及绑定)
package com.ming.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author:ming
 * @Desc: queue的配置
 */
@Configuration
public class QueueConfig {

    /**
     * 在容器中放一个队列对象
     * boot在启动的会加载这个对象
     * 帮我们在mq创建这个队列
     *
     * @return
     */
    @Bean
    public Queue helloQueue(){
        //创建队列 你可以设置一些参数  他有默认值
        return new Queue("hello-boot");
    }

    /**
     * work的队列
     *
     * @return
     */
    @Bean
    public Queue workQueue(){
        //创建队列 你可以设置一些参数  他有默认值
        return new Queue("work-boot");
    }

    /**
     * fanout的队列
     *
     * @return
     */
    @Bean
    public Queue fanoutQueue1(){
        //创建队列 你可以设置一些参数  他有默认值
        return new Queue("fanout-boot-queue1");
    }

    @Bean
    public Queue fanoutQueue2(){
        //创建队列 你可以设置一些参数  他有默认值
        return new Queue("fanout-boot-queue2");
    }

    @Bean
    public FanoutExchange fanoutEx(){
        return new FanoutExchange("fanout-boot-ex");
    }

    /**
     * 绑定交换机和队列
     *
     * @return
     */
    @Bean
    public Binding fanoutQ1Bind(){
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutEx());
    }

    @Bean
    public Binding fanoutQ2Bind() {
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutEx());
    }


    /**
     * direct的队列
     *
     * @return
     */
    @Bean
    public Queue directQueue1(){
        return new Queue("direct-boot-queue1");
    }

    @Bean
    public Queue directQueue2(){
        return new Queue("direct-boot-queue2");
    }

    @Bean
    public DirectExchange directEx(){
        return new DirectExchange("direct-boot-ex");
    }

    //绑定
    @Bean
    public Binding directQ1Bing(){
        return BindingBuilder.bind(directQueue1()).to(directEx()).with("info");
    }

    @Bean
    public Binding directQ1Bind2() {
        return BindingBuilder.bind(directQueue1()).to(directEx()).with("error");
    }

    @Bean
    public Binding directQ2Bing(){
        return BindingBuilder.bind(directQueue2()).to(directEx()).with("error");
    }	

    @Bean
    public Queue topicQueue1() {
        return new Queue("topic-boot-queue1");
    }

    @Bean
    public Queue topicQueue2() {
        return new Queue("topic-boot-queue2");
    }

    @Bean
    public TopicExchange topicEx() {
        return new TopicExchange("topic-boot-ex");
    }

    @Bean
    public Binding topicQ1Bind(){
        return BindingBuilder.bind(topicQueue1()).to(topicEx()).with("user.*");
    }

    @Bean
    public Binding topicQ2Bind(){
        return BindingBuilder.bind(topicQueue2()).to(topicEx()).with("user.#");
    }
}

3. Hello 直连模式

	//生产者
	@Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 测试hello模式
     *
     * @throws Exception
     */
    @Test
    public void testHello() throws Exception {
        rabbitTemplate.convertAndSend("hello-boot", "我是hello-boot的一个消息");
    }
//消费者
package com.ming.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 1. @Author:ming
 2. @Desc: hello模式的监听
 */
@Component
public class AHelloListener {

    /**
     * 监听消息的方法
     * 返回值一定要是void
     * 在一个方法上加一个注解 @RabbitListener(queues = "hello-boot")  queues指定队列名称
     * 监听的作用 只要在队列里面有消息 我就会消费
     *
     * @param message 消息主题
     * @param channel 信道
     */
    @RabbitListener(queues = "hello-boot")
    public void handlerHelloMsg(Message message, Channel channel){
        byte[] body = message.getBody();
        System.out.println("hello消费者正在消费" + new String(body));
        System.out.println(message.getMessageProperties());
    }
}

4. direct模式
在这里插入图片描述

	//生产者
	/**
     * 测试路由直连模式
     *
     * @throws Exception
     */
    @Test
    public void testDirect() throws Exception {
        rabbitTemplate.convertAndSend("direct-boot-ex", "info", "我是info的消息");
    }
//消费者
package com.ming.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 1. @Author:ming
 2. @Desc: 路由直连
 3. @data:2021-03-15 21:54
 */
@Component
public class DDirectListener {

    /**
     * 消费者只需要关心队列即可
     * 我不需要关心这个队列和 哪个交换机哪个路由key
     *
     * @param message
     * @param channel
     */
    @RabbitListener(queues = "direct-boot-queue1")
    public void handlerDirectMsg1(Message message, Channel channel) {
        System.out.println("我是direct-boot-queue1 消费者" + new String(message.getBody()));
    }

    /**
     * 消费者只需要关心队列即可
     * 我不需要关心这个队列和 哪个交换机哪个路由key
     *
     * @param message
     * @param channel
     */
    @RabbitListener(queues = "direct-boot-queue2")
    public void handlerDirectMsg2(Message message, Channel channel) {
        System.out.println("我是direct-boot-queue2 消费者" + new String(message.getBody()));
    }
}

5. 消息的监视

/**
     * 测试消息的监视(面试重点) 你怎么确保消息不丢失 (你怎么确保消息的状态)
     * 你不知道面试在问什么  你是在问消息的监视吗?
     *  生产者 ---- 交换机 ---- 队列 ---- 消费者
     *
     * @throws Exception
     */
    @Test
    public void testQueueWatch() throws Exception{
        //1.设置回调

        //设置消息达到交换机回调
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            //correlationData
            // ack 这是交换机确认消息的状态 true 表示交换机收到消息了
            System.out.println(correlationData);
            System.out.println(ack);
            //当ack是false时 cause 就是失败的原因
            System.out.println(cause);
        });

        //设置消息未达到队列给我们回调 linux 未达到队列 我们记录一下
        rabbitTemplate.setReturnCallback((message , replyCode, replyText, exchange, routingKey) ->{
            System.out.println(message);
            System.out.println(replyCode);
            System.out.println(replyText);
            System.out.println(exchange);
            System.out.println(routingKey);
        });

        rabbitTemplate.convertAndSend("direct-boot-ex", "info", "我是info的消息");
        // 把这个消息
        // 1. 记录在  mysql  专门有一个表 记录发的消息  记录 消息内容 消息状态 status  1
        // 2. 如果消息走了未到达队列的回调 我们可以修改数据库的状态为2
        // 3. 消息被消费了 修改数据库的状态 为3
        // 4. 数据库里面需要给一个消息的id  我们现在没有给发送的消息设置id   消息的id怎么设置 消息id的作用有哪些
        System.in.read();
    }

总结

1. Mq的作用
mq异步,削峰,限流,解耦合

2. Mq的模式
hello模式
Work模式
Fanout模式
Direct模式
Topic模式

3. 消息签收机制
签收(自定义消息的重试机制)不管 拒收

4. 消息的id
重要的场景美国消息都给一个id 这个id是消息的唯一标识
投递id(不能作为消息的唯一标识,每次消息从mq投递处理啊都会改变)

5. 消息重复消费问题
大量的数据区重操作 —布隆过滤器(redis的位图是数据结构)
每次消费的时候先判断 过滤器中是否存在

6. 消息监视(确保消息不丢失)
消息到达交换机的回调(拿不到消息的内容)
消息到达队列的回调(可以拿到消息的id记录数据库)
在发消息以后 记录数据库 status = 1
当消息未达到队列的回调 记录数据库status = 2
当消息被成功消费 以后 记录数据库状态改为 status = 3

7. 死信队列(延迟队列)
场景:自动好评 订单取消 自动确认收货
有一个延迟队列(消息都先放在延迟队列(给这个队列一 些参数))
消息死亡后按照配置 到达死信队列
我们监听死信队列即可

8. 项目中哪些地方用到了mq
秒杀用到了
记录日志 通过mq异步
发送短信 通过mq

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值