RabbitMQ基础入门

一、RabbitMQ介绍、安装及基本操作

1、RabbitMQ介绍

  • RabbitMQ是⼀个在AMQP基础上完成的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。

  • AMQP,即Advanced Message Queuing Protocol, ⼀个提供统⼀消息服务的应用层标准高级消息队列协议,是应用层协议的⼀个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有 RabbitMQ等。

  • 主要特性:

    • 保证可靠性 :使用⼀些机制来保证可靠性,如持久化、传输确认、发布确认
    • 灵活的路由功能
    • 可伸缩性:支持消息集群,多台RabbitMQ服务器可以组成⼀个集群
    • 高可用性 :RabbitMQ集群中的某个节点出现问题时队列仍然可用
    • 支持多种协议
    • ⽀持多语言客户端
    • 提供良好的管理界面
    • 提供跟踪机制:如果消息出现异常,可以通过跟踪机制分析异常原因
    • 提供插件机制:可通过插件进行多方面扩展

2、docker安装RabbitMQ

2.1、下载 RabbitMQ 镜像

下载最新镜像:

docker pull rabbitmq

2.2、创建并运行 RabbitMQ 容器

启动命令:

docker run -d -p 15672:15672 -p 5672:5672 \
-e RABBITMQ_DEFAULT_VHOST=my_vhost  \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin \
--hostname myRabbit \
--name rabbitmq \
rabbitmq

参数说明:

  • -d:表示在后台运行容器;
  • -p:将容器的端口 5672(应用访问端口)和 15672 (控制台Web端口号)映射到主机中;
  • -e:指定环境变量:
    • RABBITMQ_DEFAULT_VHOST:默认虚拟机名;
    • RABBITMQ_DEFAULT_USER:默认的用户名;
    • RABBITMQ_DEFAULT_PASS:默认的用户密码;
  • --hostname:指定主机名(RabbitMQ 的一个重要注意事项是它根据所谓的 节点名称 存储数据,默认为主机名);
  • --name rabbitmq:设置容器名称;
  • rabbitmq:容器使用的镜像名称;

查看启动情况:

docker ps -l

image-20220722112803805

设置 docker 启动的时候自动启动(可选):

docker update rabbitmq --restart=always

2.3、启动 rabbitmq_management(web管理后台)

方法一:

docker exec -it rabbitmq /bin/bash # 进入容器内部
---------------------------------
user@7b295c46c99d /: rabbitmq-plugins enable rabbitmq_management # 开启后台

方法二:

docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management # 一步搞定

2.4、访问RabbitMQ后台管理

  • 浏览器输入地址:http://ip:15672 即可访问后台管理页面,这里的 ip 为运行 RabbitMQ 所在的服务器的 IP 地址;
  • 默认的用户名和密码都是 guest如果没有在容器创建的时候指定用户名密码);
  • 但由于我们启动的时候设置了默认的用户名和密码,所以我们可以使用设置的用户名和密码登录。

image-20220722113854668

提示: 如果无法访问可以尝试开启防火墙 15672 端口:

firewall-cmd --zone=public --add-port=15672/tcp --permanent        
firewall-cmd --reload 

3、RabbitMQ逻辑结构

RabbitMQ逻辑结构
image-20220722140235252
  • 用户
  • 虚拟主机
  • 队列

4、RabbitMQ用户管理

4.1、用户管理

4.1.2、命令行用户管理
  • 在Linux中使用命令行创建用户
# 进入rabbitmq的sbin目录
cd /usr/local/rabbitmq_server-3.7.0/sbin

# 新增用户
./rabbitmqctl add_user likelong 247907lkl
  • 设置用户级别
## ⽤户级别:
## 1.administrator 可以登录控制台、查看所有信息、可以对RabbitMQ进⾏管理
## 2.monitoring 监控者 登录控制台、查看所有信息
## 3.policymaker 策略制定者 登录控制台、指定策略
## 4.managment 普通管理员 登录控制台

./rabbitmqctl set_user_tags likelong administrator
4.1.3、管理后台用户管理
1、新增用户
image-20220722142117151
2、创建虚拟机
image-20220722142329633
3、删除用户
image-20220722142515054
4、用户绑定虚拟机
image-20220722142632364

5、RabbitMQ工作方式

RabbitMQ提供了多种消息的通信方式—工作模式

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

5.1、simple简单模式

一个队列只有一个消费者

(P) -> [|||] -> (C)

生产者将消息发送到队列,消费者从队列取出数据

5.2、work工作模式

多个消费者监听同⼀个队列,虽然有多个消费者,但是一条消息只能由一个消费者消费 (资源的竞争)

img

多个消费者监听同⼀个队列,但多个消费者中只有⼀个消费者会成功的消费消息

5.3、publish/subscribe订阅模式(Fanout模式)

⼀个交换机绑定多个消息队列,每个消息队列有⼀个消费者监听(共享)

image-20220722145210688
消息生产者发送的消息可以被每⼀个消费者接收

5.4、路由模式(Direct模式)

⼀个交换机绑定多个消息队列,每个消息队列都有自己唯⼀的key,每个消息队列有⼀个消费者监听(根据路由值精准匹配)

路由模式
image-20220722145548863

5.5、topic主题模式(路由模式的一种)

同路由模式,只不过是根据路由值进行通配符匹配。通配符有#和*;
#:可以匹配多个元素(可以是0个)
*:只能匹配一个元素

6、RabbitMQ交换机和队列管理

6.1、创建队列

创建队列
image-20220722150048586

6.2、创建交换机

创建交换机
image-20220722150409796

6.3、交换机绑定队列

交换机绑定队列
image-20220722150801600image-20220722151119509

交换机绑定队列碰到的小插曲解决链接

7、消息队列三大作用

  • 流量削峰
  • 异步处理
  • 系统解耦

通过以下demo证实其作用。

二、RabbitMQ代码实践

demo主要实现:

  • 假设场景用户注册之后需要向数据库新增数据,然后调用第三方接口向用户注册所用手机号发送注册成功消息,然后调用第三方接口向用户注册所用邮箱发送注册成功邮件。
  • 假设场景用户注销之后需要向数据库修改数据,然后调用第三方接口向用户注册所用手机号发送注销成功消息,然后调用第三方接口向用户注册所用邮箱发送注销成功邮件。

场景图如下:

image-20220722154257403

实战:

项目主要依赖:

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

未使用RabbitMQ

项目结构:

image-20220722161842893

yml配置文件:

server:
  port: 9001
spring:
  application:
    name: provider
  rabbitmq:
    host: 47.96.156.51
    port: 5672
    virtual-host: host1
    username: likelong
    password: 247907lkl

controller代码如下:

注册接口:

image-20220722162651412

注销接口:

image-20220722163019782

模拟数据库,发送短信,发送邮箱一些耗时操作。

<IDEA父子项目的坑>

idea父项目pom文件才需要加<packaging>pom</packaging>,如果子项目加了这个,子项目配置文件直接会失效。(自己踩的实坑)

postman测试:

image-20220722172421889

image-20220722172530192

注销接口:

image-20220722172653526

image-20220722172721189

以上代码存在的问题:在接口内的三步操作顺序执行(改库、发短信、发邮件),并且注册和注销都需要做发送短信和邮件的操作,所以在注销和注册接口内都写了相关重复的逻辑(高耦合),并且若QPS数据量太大,直接访问接口会对服务器造成极大压力 ,严重的会造成宕机。

使用RabbitMQ

由此,引入RabbitMQ中间件进行改造,首先:

  1. 实现异步处理:上述代码可以得知,发送消息和发送邮件是同步执行,必须前面执行完后面才执行,但是根据需求,后者并不需要前者执行完才能执行,完全可以异步执行,异步执行可以大大减少消耗时间。并且用户只需要真实的注册和注销完成即可,通知只是次要的,所有接口里面实现库的操作即可,消息的操作直接交给消息监听器去处理即可。
  2. 实现系统解耦:上述代码可以得知,注册和注销都存在消息的处理(重复,高耦合),使用RabbitMQ可以将重复处理的代码交给消息监听器处理,接口只实现每个接口的核心代码即可。
  3. 实现流量削峰:当大量请求走向应用服务时,服务器压力太大,可以使用RabbitMQ,让请求先走向消息中间件,再让应用服务器去消费中间件里的消息。

改造后的场景图如下:

image-20220722174758177

注销同理。

模块化工程,服务拆分

模拟分布式,调用A服务接口,接口向RabbitMQ发送消息,B服务再去处理消息,减小A服务的服务器压力

目录结构如下:

image-20220722213048588

编写common模块代码,存放消息队列需要的常量。

image-20220722180706108

在provider服务新增一个controller类

注册接口:

image-20220722212345097

注销接口:

image-20220722212437153

相比上面的同步执行改库——发送消息——发送邮件,删除后面两步,引入rabbitTemplate,调用他的convertAndSend方法向交换机发送消息,使用常量里设置的交换机的值,和路由键的值。

路由键是6种模式中路由模式和主题模式所特有,而路由模式是绝对匹配,而主题模式是通配符匹配,此处讲讲通配符匹配规则:

通配符有#和*

#:可以匹配任意个数的元素

*:只能匹配一个元素

image-20220722183311262

所以在主题模式下,该交换机的路由值,会匹配到邮件、短信两个队列的路由值。

在消费者服务创建topic配置,配置交换机和队列的绑定关系

image-20220722215026530

注意加上@Bean和@Configuration注解,交给Spring管理。

新建两个监听器(监听邮件队列和短信队列)

image-20220722221309794


image-20220722221332934

取出生产者发送到消息队列的消息,判断类型是注册还是注销,然后走具体的处理。

@RabbitListener注解的类表示该类为监听器,参数为队列名称, @RabbitHandler表示该方法会去处理监听器获取的消息的逻辑。

postman再次测试:

image-20220722221801665

核心代码部分一秒钟即可执行完成并返回结果。

image-20220722221855808

而消费者服务在此后异步进行了短信逻辑和邮件逻辑的处理,如上图。

demo实战参考博客

三、消息队列常见问题

每种MQ都要从三个角度来分析: 生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据

消息的可靠性:从 生产者发送消息 —— 消息队列存储消息 —— 消费者消费消息 的整个过程中消息的安全性及可控性。

  • 生产者
  • 消息队列
  • 消费者

image-20220722225024794

1、生产者弄丢数据

1.1、RabbitMQ事务

RabbitMQ提供transactionconfirm模式来确保生产者不丢消息(两者只能选其一,不能同时使用)

RabbitMQ事务指的是基于客户端实现的事务管理,当在消息发送过程中添加了事务,处理效率降低几十倍甚至上百倍(用的比较少),confirm模式使用较多

transaction机制就是说,发送消息前,开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())

下面进行事务验证:

  • 注册RabbitMQ事务管理器
@Configuration
public class RabbitMQConfig {

    /**
     * 注册rabbitmq事务管理器
     *
     * @param connectionFactory 连接工厂
     * @param rabbitTemplate    rabbit模板
     * @return rabbitmq事务管理器
     */
    @Bean
    public RabbitTransactionManager rabbitTransactionManager(CachingConnectionFactory connectionFactory, RabbitTemplate rabbitTemplate) {
        // channel开启事务支持
        rabbitTemplate.setChannelTransacted(true);

        return new RabbitTransactionManager(connectionFactory);
    }
}
  • 发送者发送消息:需使用@Transactional(rollbackFor = Exception.class)事务注解

image-20220723131244945

接下来postman测试:

image-20220723131334630

image-20220723131802466

消费者未收到任何消息,事务开启成功!

注释掉int i = 1 / 0;代码

继续测试。

image-20220723131550741

image-20220723131611954

消费者收到两条消息,完美!

1.2、RabbitMQ消息确认和return机制

image-20220723132313639

消息确认机制:确认消息提供者是否成功发送消息到交换机

return机制:确认消息是否成功的从交换机分发到队列

生产者配置文件新增后面两项:

server:
  port: 9001
spring:
  application:
    name: provider
  rabbitmq:
    host: 47.96.156.51
    port: 5672
    virtual-host: host1
    username: likelong
    password: 247907lkl
    publisher-confirm-type: simple # 开启消息确认
    publisher-returns: true # 开启return机制

confirm监听:

@Component
public class MyConfirmListener implements RabbitTemplate.ConfirmCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //init方法会在创建当前MyConfirmListener对象之后执行
    @PostConstruct //加上该注解的方法,会在实例化对象之后执行
    public void init() {
        rabbitTemplate.setConfirmCallback(this);
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        //参数b表示消息确认结果  参数s表示发送的消息
        if (b) {
            System.out.println("消息成功发送到交换机!");
        } else {
            System.out.println("消息发送到交换机失败!");
            //消息发送失败,重发消息
            rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTER_KEY_EXCHANGE, s);
        }
    }
}

return监听:

@Component
public class MyReturnListener implements RabbitTemplate.ReturnsCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnsCallback(this);
    }

    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        //只要此方法执行说明交换机分发消息到队列失败
        System.out.println("消息从交换机分发到队列失败");
        //获取交换机
        String exchange = returnedMessage.getExchange();
        //获取路由key
        String routingKey = returnedMessage.getRoutingKey();
        //获取发送消息
        String msg = returnedMessage.getMessage().toString();
        //失败就再次发送
        rabbitTemplate.convertAndSend(exchange,routingKey,msg);
    }
}

新增方法:

image-20220723140857268

测试:

image-20220723140917043

image-20220723140950248

2、消息队列弄丢数据

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发
那么如何持久化呢,其实也很容易,就下面两步
1、queue的持久化标识durable设置为true,则代表是一个持久的队列
2、发送消息的时候将deliveryMode=2
这样设置以后,rabbitMQ就算挂了,重启后也能恢复数据

3、消费者弄丢数据

消费者丢数据一般是因为采用了默认自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时RabbitMQ会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失该消息
至于解决方案,采用手动确认消息即可。

手动ack实现如下:

消费者配置文件新增如下红框内容:

image-20220723151057949


image-20220723151325454

注意点:发送者发送消息最好用json格式字符串,以便消费者解析消费。

4、消息重复消费

消息重复的原因

消息重复的原因有两个:1.生产时消息重复,2.消费时消息重复。

  • 生产时消息重复
    由于生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认,实际上MQ已经接收到了消息。这时候生产者就会重新发送一遍这条消息。

  • 消费时消息重复

    消费者消费成功后,再给MQ确认的时候出现了网络波动,MQ没有接收到确认,为了保证消息被消费,MQ就会继续给消费者投递之前的消息。这时候消费者就接收到了两条一样的消息。

消息消费的幂等性——多次消费的执行结果时相同的 (避免重复消费)

如何保证消息幂等性

让每个消息携带一个全局的唯一ID,即可保证消息的幂等性,具体消费过程为:

消费者获取到消息后先根据id去查询redis/db是否存在该消息
如果不存在,则正常消费,消费完毕后写入redis/db
如果存在,则证明消息被消费过,直接丢弃

四、消息队列作用/使用场景总结

1、系统解耦

场景说明:用户下单之后,订单系统要通知库存系统

传统方式:订单系统直接调用库存系统提供的接口,如果库存系统出现故障会导致订单系统失败
image-20220723163338671
使用消息队列:
image-20220723163517379

2、异步处理

场景说明:用户注册成功之后,需要发送短信和邮箱提醒

传统方式:用户注册,数据库新增成功后,发送邮件,发送邮件成功后,发送短信,最后等发送短信成功之后才会给用户响应
image-20220723163757709
使用消息队列:
image-20220723164038291

3、消息通信

场景说明:应用系统之间的通信,例如聊天室

聊天室
image-20220723164203578

4、流量削峰

场景说明:秒杀业务

秒杀业务:大量的请求不会主动请求秒杀业务,而是存放在消息队列(缓存)
image-20220723164436920

5、日志处理

场景说明:系统中大量的日志处理

日志搜集处理
image-20220723164540169
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不进大厂不改名二号

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

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

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

打赏作者

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

抵扣说明:

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

余额充值