RabbitMQ基础入门教程(从安装到SpringBoot整合)

RabbitMQ

1.安装

下载linux下的erlang压缩包,rabbitMQ-server压缩包

因为RabbitMQ底层是用Erlang语言来写的,所以安装RabbitMQ之前要先安装Erlang环境

1.安装Erlang环境

#rpm -ivh erlang-23.2.3-1.el7.x86_64.rpm 

image-20220518164235379

2.用yum 安装的方式安装socat 插件

# yum install -y socat

image-20220518164735158

3.安装RabbitMQ

#rpm -ivh rabbitmq-server-3.8.11-1.el7.noarch.rpm 

image-20220518164937278

4.检查rabbitmq是否启动

#ps -ef|grep rabbit

image-20220518165053295

如图表示没有启动

5.启动RabbitMQ

#rabbitmq-server -detached

6.启动rabbit之后查看后台服务

image-20220518165334078

7.关闭RabbitMQ

#rabbitmqctl stop

2.启动与停止

直接启动

#rabbitmq-server -detached

直接关闭

#rabbitmqctl stop

启动服务

#systemctl start rabbitmq-server

image-20220518172700857

查看rabbit服务状态

#systemctl status rabbitmq-server

image-20220518172806003

设置开启启动rabbitmq

#systemctl enable rabbitmq-server

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IRJi69Oo-1657363169530)(https://raw.githubusercontent.com/yuan-boss/blog_file/master/images/202207091837683.png)]

关闭开机自启动

#systemctl stop rabbitmq-server

image-20220518173028880

3.rabbitmq默认插件

#rabbitmq-plugins list

image-20220518170603069

1.启动控制台插件

#rabbitmq-plugins enable rabbitmq_management

2.启动插件之后在宿主机访问控制台

服务器ip+端口(15672)

image-20220518170750238

默认 rabbitmq 给我们提供了一个guest的账户 ,密码也是guest

image-20220518170902426

但是现在 rabbitmq 主机没有在windows上 所以我们必须使用ip访问,不能用localhost,所以我们需要使用命令再创建一个rabbitmq的管理员用户:

#rabbitmqctl add_user root root

image-20220518171100614

管理员用户创建好之后,再给用户管理员的角色:

#rabbitmqctl set_user_tags root administrator

image-20220518171147144

再使用root用户就可以登录控制台了

image-20220518171248910

登录控制台之后,别忘记给用户设置权限

image-20220618160859366

2.Docker安装RabbitMQ

$ docker run -d --hostname my-rabbit --name some-rabbit -e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=root -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:3-management

3.生产者

1.创建连接工厂,给连接工厂配置账号密码等信息
2.通过连接工厂创建了连接
3.通过连接创建一个通道
4. 为通道声明队列(队列名字,对列是否持久化,是否具有排他性,最后一个消费者消费完之后是否自动删除队列,该队列携带什么参数)
5.准备消息内容
6.发送消息给队列
7.关闭通道
8.关闭连接

声明队列的核心代码解释

/**
             * @Params1 队列的名称
             * @Params2 是否要持久化durable,默认是不持久化,为false,所谓持久化就是重启服务之后是否会保留队列,非持久化队列会存盘,但是随着服务器重启                        会丢失
             * @Params3 排他性,是否独占独立
             * @Params4 是否自动删除,随着最后一个消费者消费完消息之后是否把队列删除
             * @Params5 携带一些附加参数
             */
channel.queueDeclare(queueName,false,false,false,null);

4.面试题

1.为什么RabbitMQ是基于通道(channal)处理,而不是连接(connection)?

connection是一个短连接,短连接会经过三次握手四次挥手,这个过程很慢,耗费资源,耗时长,连接开关会造成很大的性能开销
所以connection在 TCP/IP 的基础之上开发一个长连接的信道,即为channal,channal是网络信道,几乎所有的操作都在chanal中进行,一个connection可以开启多个channal,从而大大提高了性能

2.Rabbitmq为什么需要信道,为什么不是TCP直接通信

1、TCP的创建和销毁,开销大,创建要三次握手,销毁要4次分手。
2、如果不用信道,那应用程序就会TCP连接到Rabbit服务器,高峰时每秒成千上万连接就会造成资源的巨大浪费,而且==底层操作系统每秒处理tcp连接数也是有限制的,==必定造成性能瓶颈。
3、信道的原理是一条线程一条信道,多条线程多条信道同用一条TCP连接,一条TCP连接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能瓶颈。

3.queue队列到底在消费者创建还是生产者创建?

1:一般建议是在rabbitmq操作面板创建。这是一种稳妥的做法。
2∶按照常理来说,确实应该消费者这边创建是最好,消息的消费是在这边。这样你承受一个后果,可能我生产在生产消息可能会丢失消息。
3:在生产者创建队列也是可以,这样稳妥的方法,消息是不会出现丢失。
4:如果你生产者和消费都创建的队列,谁先启动谁先创建,后面启动就覆盖前面的

4.可以存在没有交换机的队列吗?

不可能存在,虽然没有指定交换机,但是一定会存在一个默认的交换机,因为没有交换机队列就不会存在消息
交换机负责消息的接受,队列不会接收消息,是交换机投递消息给队列,而不是队列去接收消息
//6.发送消息给队列
/**
* @Params1 交换机(最好指定一个叫交换机名字,不然就会使用默认的交换机)
* @Params2 队列,路由key
* @Params3 消息的状态控制
* @Params4 消息主题
*/
channel.basicPublish("",queueName, null,message.getBytes());

5.模式

1.简单模式(default)

通过默认交换机投递消息给指定队列

2.发布订阅模式(fanout)

通过交换机与队列进行绑定,交换机会将消息投递到所有与之绑定的队列
类似于一些粉丝关注一个博主之后,这个博主发布视频,所有订阅这个博主的粉丝都会看到这个视频

3.路由模式(direct)

这个模式是发布订阅模式的延伸,本质还是将交换机与队列进行绑定,但是绑定的同时还要加上 Routing Key ,当交换机投递消息时,带上某个路由key,这条消息就会投递到有这个路由key的队列
可以理解为交换机投递信息给队列的时候要根据某个条件投递,交换机只会投递给满足条件的队列

场景:
当一个用户注册某个平台的账号,会把注册通知发送到邮箱,短信与微信,会对用户造成很大的不便,如果指定某个路由key,在发送注册通知的时候在路由key中带上emai,那么这个通知就只会发到带有路由key “email”的队列中

4.主题模式(tocpic)

这个模式是路由模式的延伸
就是在路由模式的基础上,添加模糊条件绑定功能,其中有#,*两种符号来控制条件
符号#:表示0个或一个或多个
符号*:表示一定要有一个并且只有一个
示例:
#.com.#: 满足这个条件的路由key是(com 或者 xxx.com.xxx或者xxx.com.xxx.xxx等等)
*.com.*:满足这个条件的路由key是(xxx.com.xxx)
#.com.*:满足这个条件的路由key是(xxx.xxx.com.x或者com.xxx)

5.headers模式

这个模式也是通过条件来进行发布消息,这个条件是就是参数
交换机先通过参数绑定队列,比如X=1绑定到queue1
在投递消息的时候携带x=1的参数即可将消息投递到queue1

6.Work模式

  • 轮询分发

    轮询分发就是指按均分配,消息分发默认就是轮询分发
    轮询分发不依赖于消费者的处理速度,即使两个消费者处理速度相差很大,他们处理的消息数量也是一样的
    轮询分发下,消费者消费的时候既可以设置为手动,应可以设置为自动应答
    
  • 公平分发

    公平分发就是按劳分配
    消息分发取决于消费者的处理速度,如果消费者处理快,那这个消费者就能处理更多的消息,反之处理更少的消息
    公平分发下,消费者消费的时候必须设置为手动应答,如果autoAck参数设置为true,那就会变成轮询分发
     //设置为手动应答
    finalChannel.basicAck(message.getEnvelope().getDeliveryTag(),false);
    
    另外在消费者消费之前,我们还需要设置一个qos指标
    //定义指标,qos=1
    finalChannel.basicQos(1);
    里面的参数过大的话消费者的消费就容易变成平均消费
    比如10条消息,将指标设置为10的话,两个消费者都是各消费5条消息
    
    

思考:

1.假设选择 fanout 模式的交换机,发布消息的时候带上路由key,是否只会向带有该路由key的队列发送信息呢?

不会,fanout模式下不会根据路由key投递,就算带上路由key,也没有意义,fanout模式下路由key无效

2.假设选择headers模式的交换机,发布消息的时候带上路由key,是否会向带有该路由key的队列发送信息呢?

不会,即使带上路由key,也不会投递信息到有该路由key的队列,header模式下只有参数条件有效

3.假设生产者发送消息给一个不存在的交换机,能成功吗?

不能成功,会报错(......reply-code=404, reply-text=NOT_FOUND - no exchange ...... )

4.假设消费者消费一个不存在的队列,能成功吗?

不能成功,会报错(...... reply-code=404, reply-text=NOT_FOUND - no queue......)

5.basicPublish()方法的第二个参数到底是路由key 还是 队列名?

如一下代码:

/**
             * @Params1 交换机(最好指定一个叫交换机名字,不然就会使用默认的交换机)
             * @Params2 队列,路由key
             * @Params3 消息的状态控制
             * @Params4 消息主题
             */
for (int i = 0; i < 10; i++) {
    String message = ""+(i+1);
    channel.basicPublish("",queueName, null,message.getBytes());
}
解答:
如果第一个参数交换机指定了,那第二个参数就是指路由key
如果第一个参数交换机未指定,默认为默认交换机,那么第二个参数表示队列名

6.SpringBoot整合RabbitMQ(direct模式)

整合流程

  • 1.在application.yml文件中配置用户名与密码,虚拟主机地址,切记服务器需要开放5672端口
  • 2.使用配置类声明交换机,队列并且绑定交换机与队列
  • 3.生产者发送消息
  • 4.消费者通过消费方法上的 @RabbitListener(queues = {"duanxin.direct.queue"})注解时刻监听队列
  • 5.先启动消费者服务等待接收信息(注意流程2一般是在消费者模块中配置)
  • 6.启动生产者服务

导入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
                <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>

生产者

1.application.yml配置

server:
  port: 8080
spring:
  rabbitmq:
    username: root
    password: root
    host: 175.178.151.38
    port: 5672
    virtual-host: /

2.配置类(声明交换机,队列,绑定交换机与队列)

package com.yuan.config;

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

/**
 * @module:
 * @description:
 * @author: yuan_boss
 * @create: 2022-07-08 16:13
 **/
@Configuration
public class DirectRabbitMqConfiguration {
    //1.声明注册direct模式的交换机

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("direct_order_exchange",true,false);
    }

    //2.声明队列: sms.direct.queue,email.direct.queue,duanxin.direct.queue

    @Bean
    public Queue directSmsQueue(){
        return new Queue("sms.direct.queue",false);
    }
    @Bean
    public Queue directEmailQueue(){
        return new Queue("email.direct.queue",false);
    }
    @Bean
    public Queue directDuanxinQueue(){
        return new Queue("duanxin.direct.queue",false);
    }

    //3.完成绑定关系(队列和交换机完成绑定关系)

    @Bean
    public Binding directSmsBinding(){
        return BindingBuilder.bind(directSmsQueue()).to(directExchange()).with("sms");
    }
    @Bean
    public Binding directEmailBinding(){
        return BindingBuilder.bind(directEmailQueue()).to(directExchange()).with("email");
    }
    @Bean
    public Binding directDuanxinBinding(){
        return BindingBuilder.bind(directDuanxinQueue()).to(directExchange()).with("duanxin");
    }

}

3.业务类----生成订单消息

package com.yuan.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * @module:
 * @description:
 * @author: yuan_boss
 * @create: 2022-07-08 15:56
 **/
@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 模拟用户下单
     */
    public void makerOrder(String userid,String productId,int num){

        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:"+orderId);
        //3.通过MQ完成消息的分发
        /**
         * @Params1 交换机
         * @Params2 路由key/队列名(如果没指定交换机,那就是队列名,如果指定了交换机,那就是路由key)
         * @Params3 消息内容
         */
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);

    }


    /**
     * 模拟用户下单
     */
    public void makerOrderDirect(String userid,String productId,int num){

        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:"+orderId);
        //3.通过MQ完成消息的分发
        /**
         * @Params1 交换机
         * @Params2 路由key/队列名(如果没指定交换机,那就是队列名,如果指定了交换机,那就是路由key)
         * @Params3 消息内容
         */
        String exchangeName = "direct_order_exchange";
        rabbitTemplate.convertAndSend(exchangeName,"email",orderId);
        rabbitTemplate.convertAndSend(exchangeName,"duanxin",orderId);

    }
}

消费者

1.application.yml配置

server:
  port: 8081
spring:
  rabbitmq:
    username: root
    password: root
    host: 175.178.151.38
    port: 5672
    virtual-host: /

2.配置类(声明交换机,队列,绑定交换机与队列)

package com.yuan.service.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @module:
 * @description:
 * @author: yuan_boss
 * @create: 2022-07-08 16:13
 **/
@Configuration
public class DirectRabbitMqConfiguration {
    //1.声明注册direct模式的交换机

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("direct_order_exchange",true,false);
    }

    //2.声明队列: sms.direct.queue,email.direct.queue,duanxin.direct.queue

    @Bean
    public Queue smsQueue(){
        return new Queue("sms.direct.queue",false);
    }
    @Bean
    public Queue emailQueue(){
        return new Queue("email.direct.queue",false);
    }
    @Bean
    public Queue duanxinQueue(){
        return new Queue("duanxin.direct.queue",false);
    }

    //3.完成绑定关系(队列和交换机完成绑定关系)

    @Bean
    public Binding smsBinding(){
        return BindingBuilder.bind(smsQueue()).to(directExchange()).with("sms");
    }
    @Bean
    public Binding emailBinding(){
        return BindingBuilder.bind(emailQueue()).to(directExchange()).with("email");
    }
    @Bean
    public Binding duanxinBinding(){
        return BindingBuilder.bind(duanxinQueue()).to(directExchange()).with("duanxin");
    }

}

3.业务类—接收订单信息

注意:消费者类上的@RabbitListener(queues = {"duanxin.direct.queue"})注解可以取代@RabbitHandler

  • DirectDuanxinConsumer
@RabbitListener(queues = {"duanxin.direct.queue"})
@Service
public class DirectDuanxinConsumer {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("duanxin direct ---接收到了订单信息:————>"+message);
    }
}
  • DirectEmailConsumer
@RabbitListener(queues = {"email.direct.queue"})
@Service
public class DirectEmailConsumer {
    @RabbitHandler
    public void receiveMessage(String mess age){
        System.out.println("email direct ---接收到了订单信息:————>"+message);
    }
}
  • DirectSMSConsumer
@RabbitListener(queues = {"sms.direct.queue"})
@Service
public class DirectSMSConsumer {
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms direct ---接收到了订单信息:————>"+message);
    }
}

思考

1.声明交换机,队列,绑定交换机与队列的配置类如果只写一个,应该写在哪个模块呢?

其实两个模块都可以写上,不过最合适是写在消费者模块
因为消费者模块是最先启动的服务,要一直监听其绑定的队列,如果监听的队列不存则会报错

7.高级阶段(TTL,死信队列)

TTL,全称为 Time to Live ,即生存时间

TTL队列过期

TTL就是给一个队列设置过期时间,当一个消息在指定时间内没有被消费,将会被队列移除,TTL一般与死信队列搭配使用,移除的消息是放在死信队列中

TTL队列就是在声明队列的时候传一个map产出,该map的key为`x-message-ttl`
    @Bean
    public Queue ttlDirectQueue(){
        Map<String,Object> args = new HashMap<>();
        args.put("x-message-ttl",5000);//这里一定是int类型,单位是毫秒
        return new Queue("ttl.direct.queue",true,false,false,args);
    }

对某条消息设置过期时间

我们不仅可以将整个队列设置TTL,从而给该队列里的所有消息设置TTL

还可以在发送消息的时候单独对某个消息进行设置TTL(其中的代码核心是MessagePostProcessor类,该类可以对你发的消息进行加工,设置过期时间,编码等信息,如下代码

 /**
     * 模拟用户下单,TTL队列,单纯给某一条消息设置过期时间
     */
    public void makerOrderMessageTtl(String userid,String productId,int num){

        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:"+orderId);
        //3.通过MQ完成消息的分发
        /**
         * @Params1 交换机
         * @Params2 路由key/队列名(如果没指定交换机,那就是队列名,如果指定了交换机,那就是路由key)
         * @Params3 消息内容
         */
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttlmessage";

        //给消息设置过期时间
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("5000");
                message.getMessageProperties().setContentType("UTF-8");
                return message;
            }
        };
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId,messagePostProcessor);
    }

队列TTL与消息TTL的思考

1.假设对一个队列设置了TTL,往这个队列投递的消息也设置了过期时间,那么这条消息是存留时间是多少?

该消息的存留时间取决于最短的过期时间
假设队列TTL为3秒,消息TTL为5秒,那么消息存留时间为3秒,因为3秒之后,队列会移除里面的所有消息
假设队列TTL为5秒,消息TTL为3秒,那么消息存留时间为3秒,因为3秒之后,该消息会自动移除

2.队列TTL与消息TTL的差异是什么?

队列TTL可以和死信队列配合,当队列里面的消息生存时间一旦超过设置的值TTL值,就称为dead message,会被投递到死信队列,消费者将无法从原来的队列中收到该消息
消息TTL则无法与死信队列配合

死信队列

DLX,全称为Dead-Letter-Exchange,可以称之为死信交换机,也有人称之为死信邮箱,当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就称为死信队列,消息变成死信,可能是以下原因:

  • 消息被拒绝
  • 消息过期
  • 队列达到最大长度

如何绑定死信队列呢?

在普通队列中设置有关死信队列的参数,`x-dead-letter-exchange`代表死信交换机,`x-dead-letter-routing-key`代表死信交换机与死信对列关联的key
        @Bean
    public Queue ttlDirectQueue(){
        Map<String,Object> args = new HashMap<>();
        args.put("x-message-ttl",5000);//这里一定是int类型,单位是毫秒
        //给死信队列绑定与死信交换机绑定
        args.put("x-dead-letter-exchange","dead_direct_exchange");
        args.put("x-dead-letter-routing-key","dead");//fanout模式不需要配置
        return new Queue("ttl.direct.queue",true,false,false,args);
    }

内存磁盘预警

内存默认是服务器内存的0.4,当RabbitMQ内存占用达到0.4的时候,web监控页面会爆红,这个时候所有队列将会挂起,无法接收消息,这个时候我们可以到服务器设置RabbitMQ的内存阈值,一般设置为0.4-0.7的服务器内存

内存换页

当内存快到达阈值的时候,默认会把0.5的数据转移到磁盘,从而减轻内存压力,一般设置为0.4,0.5
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yuan_boss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值