栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

RabbitMQ

文章目录
  • RabbitMQ
    • 1.安装
      • 1.安装Erlang环境
      • 2.用yum 安装的方式安装socat 插件
      • 3.安装RabbitMQ
      • 4.检查rabbitmq是否启动
      • 5.启动RabbitMQ
      • 6.启动rabbit之后查看后台服务
      • 7.关闭RabbitMQ
    • 2.启动与停止
    • 3.rabbitmq默认插件
      • 1.启动控制台插件
      • 2.启动插件之后在宿主机访问控制台
    • 2.Docker安装RabbitMQ
    • 3.生产者
    • 4.面试题
    • 5.模式
      • 思考:
    • 6.SpringBoot整合RabbitMQ(direct模式)
      • 整合流程
      • 生产者
      • 消费者
      • 思考
    • 7.高级阶段(TTL,死信队列)
      • TTL队列过期
      • 对某条消息设置过期时间
      • 队列TTL与消息TTL的思考
      • 死信队列
      • 内存磁盘预警

1.安装

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

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

1.安装Erlang环境
#rpm -ivh erlang-23.2.3-1.el7.x86_64.rpm 

2.用yum 安装的方式安装socat 插件
# yum install -y socat

3.安装RabbitMQ
#rpm -ivh rabbitmq-server-3.8.11-1.el7.noarch.rpm 

4.检查rabbitmq是否启动
#ps -ef|grep rabbit

如图表示没有启动

5.启动RabbitMQ
#rabbitmq-server -detached
6.启动rabbit之后查看后台服务

7.关闭RabbitMQ
#rabbitmqctl stop
2.启动与停止

直接启动

#rabbitmq-server -detached

直接关闭

#rabbitmqctl stop

启动服务

#systemctl start rabbitmq-server

查看rabbit服务状态

#systemctl status rabbitmq-server

设置开启启动rabbitmq

#systemctl enable rabbitmq-server

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

关闭开机自启动

#systemctl stop rabbitmq-server

3.rabbitmq默认插件
#rabbitmq-plugins list

1.启动控制台插件
#rabbitmq-plugins enable rabbitmq_management
2.启动插件之后在宿主机访问控制台

服务器ip+端口(15672)

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

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

#rabbitmqctl add_user root root

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

#rabbitmqctl set_user_tags root administrator

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

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

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.关闭连接

声明队列的核心代码解释

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.发送消息给队列

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 还是 队列名?

如一下代码:

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.启动生产者服务

导入依赖

        
            org.springframework.boot
            spring-boot-starter-amqp
        
                
            org.springframework.amqp
            spring-rabbit-test
            test
        
生产者

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;


@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;


@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完成消息的分发
        
        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完成消息的分发
        
        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;


@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 args = new HashMap<>();
        args.put("x-message-ttl",5000);//这里一定是int类型,单位是毫秒
        return new Queue("ttl.direct.queue",true,false,false,args);
    }
对某条消息设置过期时间

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

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

 
    public void makerOrderMessageTtl(String userid,String productId,int num){

        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:"+orderId);
        //3.通过MQ完成消息的分发
        
        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 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
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1007671.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号