RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)

1.MQ简介

MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器。多用于分布式系统 之间进行通信。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

2.为什么要用 MQ

1.流量消峰 没使用MQ

编辑切换为居中

添加图片注释,不超过 140 字(可选)

使用了MQ

编辑切换为居中

添加图片注释,不超过 140 字(可选)

2.应用解耦

编辑切换为居中

添加图片注释,不超过 140 字(可选)

3.异步处理 没使用MQ

编辑切换为居中

添加图片注释,不超过 140 字(可选)

使用了MQ

编辑切换为居中

添加图片注释,不超过 140 字(可选)

3.常见的MQ对比

编辑切换为居中

添加图片注释,不超过 140 字(可选)

先学习RabbitMQ,后面可以再学学RocketMQ和Kafka

4.RabbitMQ的安装(linux:centos7环境,我使用的是docker容器进行安装的,也可以使用其他方式 >>>> 非docker方式安装RabbitMQ)

一、下载镜像

docker search RabbitMQ

进入docker hub镜像仓库地址:Docker Hub

搜索rabbitMq,进入官方的镜像,可以看到以下几种类型的镜像;我们选择带有“mangement”的版本(包含web管理页面);

拉取镜像

 
 

docker pull rabbitmq:management

二、安装和web界面启动

镜像创建和启动容器

 
 

docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management

说明:

  • -d 后台运行容器;

  • --name 指定容器名;

  • -p 指定服务运行的端口(5672:应用访问端口;15672:控制台Web端口号);

  • --hostname 主机名(RabbitMQ的一个重要注意事项是它根据所谓的 “节点名称” 存储数据,默认为主机名);

查看所有正在运行容器

 
 

docker ps -a

删除指定容器

 
 

docker rm ID/NAME

删除所有闲置容器

 
 

docker container prune

重启docker

 
 

systemctl restart docker

重启启动RabbitMQ

 
 

docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management

开启防火墙15672端口

 
 

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

停止RabbitMQ容器

 
 

- 命令: docker stop rabbitmq

启动RabbitMQ容器

 
 

- 命令:docker start rabbitmq

重启RabbitMQ容器

 
 

- 命令:docker restart rabbitmq

三、测试 http://linuxip地址:15672,这里的用户名和密码默认都是guest

编辑切换为居中

添加图片注释,不超过 140 字(可选)

四、进入rabbitmq容器

 
 

docker exec -it rabbitmq /bin/bash

五、添加新的用户

创建账号

 
 

rabbitmqctl add_user 【用户名】 【密码】

设置用户角色

 
 

rabbitmqctl set_user_tags admin administrator

设置用户权限

 
 

rabbitmqctl set_permissions -p "/" qbb ".*"".*"".*"

查看当前用户角色、权限

 
 

rabbitmqctl list_users

安装好RabbitMQ后如果需要熟悉里面的操作,大家可以参考官方网站

5.RabbitMQ提供了7种工作模式

编辑

添加图片注释,不超过 140 字(可选)

6.RabbitMQ入门之简单模式(Java操作RabbitMQ)

1.创建一个普通的maven项目

编辑切换为居中

添加图片注释,不超过 140 字(可选)

2.在pom.xml中导入相关依赖

 
 

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.qbb</groupId> <artifactId>java-mq-producer</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.14.2</version> </dependency> </dependencies> </project>

3.编写生产者发送消息

 
 

package com.qbb.simple; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; /** * @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit) * @version 1.0 * @date 2022-03-28 16:25 * @Description:生产者 */ public class SimpleProducer { public static void main(String[] args) { try { // 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("192.168.137.72"); factory.setPort(5672); factory.setUsername("qbb"); factory.setPassword("qbb"); factory.setVirtualHost("/"); // 获取连接对象 Connection connection = factory.newConnection(); // 获取channel Channel channel = connection.createChannel(); // 我们将消息发送到队列中,前提是我们要有一个队列,所以先声明一个队列 /** * String queue : 队列名称 * boolean durable : 队列是否持久化 * boolean exclusive : 是否独占本次连接,默认true * boolean autoDelete : 是否自动删除,最后一个消费者断开连接以后,该队列是否自动删除 * Map<String, Object> arguments : 队列其它参数 */ channel.queueDeclare("simple-queue", false, false, false, null); // 发送消息 /** * String exchange : 交换机名称,发送到哪个交换机 * String routingKey : 路由key是哪个 * BasicProperties props : 其他参数信息 * byte[] body : 要发送的消息 */ String message = "hello QiuQiu RabbitMQ"; channel.basicPublish("", "simple-queue", null, message.getBytes()); System.out.println("消息发送完毕"); // 释放资源 channel.close(); connection.close(); } catch (Exception e) { e.printStackTrace(); } } }

编辑切换为居中

添加图片注释,不超过 140 字(可选)

编辑切换为居中

添加图片注释,不超过 140 字(可选)

4.编写消费者接收消息

 
 

package com.qbb.simple; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit) * @version 1.0 * @date 2022-03-28 18:11 * @Description:消费者 */ public class SimpleConsumer { public static void main(String[] args) { try { // 创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("192.168.137.72"); factory.setPort(5672); factory.setUsername("qbb"); factory.setPassword("qbb"); factory.setVirtualHost("/"); // 获取连接对象 Connection connection = factory.newConnection(); // 获取channel通道 Channel channel = connection.createChannel(); // 声明队列 /** * String queue, * boolean autoAck, * Consumer callback */ Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body); System.out.println(msg); } }; //监听队列,第二个参数false,手动进行ACK channel.basicConsume("simple-queue", true, consumer); // 注意消费者端不要释放资源,需要一直监控着队列中的消息 } catch (Exception e) { e.printStackTrace(); } } }

编辑切换为居中

添加图片注释,不超过 140 字(可选)

注意:我们可以看到控制台报了一个错,应该是少了个slf4j的依赖,我们导入就好了

 
 

<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> <scope>compile</scope> </dependency>

7.消息确认机制

我们查询图形化界面发现消息一经消费,就被删除了. 那么RabbitMQ怎么知道消息已经被我们消费了呢? 如果消费者领取消息后,还没执行操作就挂掉了呢? 或者抛出了异常?消息消费失败,但是 RabbitMQ 无从得知,这样消息就丢失了!

因此,RabbitMQ 有一个 ACK 机制。 当消费者获取消息后,会向 RabbitMQ 发送回执 ACK, 告知消息已经被接收。 不过这种回执 ACK 分两种情况:

  • 自动 ACK:消息一旦被接收,消费者自动发送 ACK

  • 手动 ACK:消息接收后,不会发送 ACK,需要手动调用

  • 如果消息不太重要,丢失也没有影响,那么自动 ACK 会比较方便

  • 如果消息非常重要,不容丢失。那么最好在消费完成后手动 ACK,否则接收消息后 就自动 ACK,RabbitMQ 就会把消息从队列中删除。如果此时消费者宕机,那么消 息就丢失了。

手动在consumer中制造一个异常,发现消息依旧被消费了

编辑切换为居中

添加图片注释,不超过 140 字(可选)

测试一下手动ACK

 
 

// 修改consumer端的代码 Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body); int a = 1 / 0; System.out.println(msg); //手动进行ACK channel.basicAck(envelope.getDeliveryTag(), false); } }; //监听队列,第二个参数false,手动进行ACK channel.basicConsume("simple-queue", false, consumer);

可以看出即使出现了异常消息依旧不会被消费丢失

编辑切换为居中

添加图片注释,不超过 140 字(可选)

去掉异常重新启动consumer发现消息又被消费了

编辑切换为居中

添加图片注释,不超过 140 字(可选)

8.RabbitMQ入门之工作队列模式(Java操作RabbitMQ)

与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息 应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

在前面的工程基础上创建两个包,继续编写代码 我们把获取connection对象抽取一个utils工具类

1.编写生产者发送消息

 
 

package com.qbb.workqueue; import com.qbb.utils.MQUtil; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; /** * @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit) * @version 1.0 * @date 2022-03-28 19:09 * @Description: */ public class WorkQueueProducer { public static void main(String[] args) { try { Connection connection = MQUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("work-queue", false, false, false, null); // 发送消息 for (int i = 0; i < 20; i++) { String message = "hello QiuQiu work-queue:"+i; channel.basicPublish("", "work-queue", null, message.getBytes()); } // 释放资源 channel.close(); connection.close(); } catch (Exception e) { e.printStackTrace(); } } }

2.编写消费者接收消息

 
 

**消费者1** package com.qbb.workqueue; import com.qbb.utils.MQUtil; import com.rabbitmq.client.*; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; /** * @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit) * @version 1.0 * @date 2022-03-28 19:21 * @Description: */ public class WorkQueueConsumer1 { public static void main(String[] args) { try { Connection connection = MQUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("work-queue", false, false, false, null); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 消费者1消费消息 try { // 睡50ms秒模拟,此服务性能差一点 Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } String msg = new String(body); System.out.println("消费者1消费消息 = " + msg); channel.basicAck(envelope.getDeliveryTag(), false); } }; channel.basicConsume("work-queue", false, consumer); } catch (Exception e) { e.printStackTrace(); } } }

 
 

**消费者2** package com.qbb.workqueue; import com.qbb.utils.MQUtil; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeUnit; /** * @author QiuQiu&LL (个人博客:https://www.cnblogs.com/qbbit) * @version 1.0 * @date 2022-03-28 19:21 * @Description: */ public class WorkQueueConsumer2 { public static void main(String[] args) { try { Connection connection = MQUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare("work-queue", false, false, false, null); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 消费者2消费消息 String msg = new String(body); System.out.println("消费者2消费消息 = " + msg); channel.basicAck(envelope.getDeliveryTag(), false); } }; channel.basicConsume("work-queue", false, consumer); } catch (Exception e) { e.printStackTrace(); } } }

编辑

添加图片注释,不超过 140 字(可选)

编辑

添加图片注释,不超过 140 字(可选)

可以发现,两个消费者各自消费了 25 条消息,而且各不相同,这就实现了任务的分发。 但是我现在想让性能差一点的服务器少处理点消息,实现能者多劳怎么办呢? 好办

在比较慢的消费者创建队列后我们可以使用 basicQos 方法和 prefetchCount = n ,告诉RabbitMQ每次给我发送一个消息等我处理完这个消息再给我发一个,一次一个的发消息

 
 

... WorkQueueConsumer1.java ... // 设置每次拉取一条消息消费 channel.basicQos(1);

编辑

添加图片注释,不超过 140 字(可选)

编辑切换为居中

添加图片注释,不超过 140 字(可选)

这样就解决了服务器性能差异问题

8.RabbitMQ入门之发布订阅模式|Publish/Subscribe(Java操作RabbitMQ)

一次同时向多个消费者发送消息,一条消息可以被多个消费者消费

编辑切换为居中

添加图片注释,不超过 140 字(可选)

在订阅模型中,多了一个 exchange 角色,而且过程略有变化:

  • P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给 X(交换机)

  • C:消费者,消息的接受者,会一直等待消息到来。

  • Queue:消息队列,接收消息、缓存消息。

  • Exchange:交换机,图中的 X。 一方面,接收生产者发送的消息。另一方面,知道如 何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何 操作,取决于 Exchange 的类型。

Exchange 有常见以下 3 种类型:

  • Fanout:广播,将消息交给所有绑定到交换机的队列

  • Direct:定向,把消息交给符合指定 routing key 的队列

  • Topic:通配符,把消息交给符合 routing pattern(路由模式) 的队列 Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!

在广播模式下,消息发送流程是这样的:

  • 可以有多个消费者 -每个消费者有自己的 queue(队列)

  • 每个队列都要绑定到 Exchange(交换机)

  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定

  • 交换机把消息发送给绑定过的所有队列

  • 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

Fanout 交换机

编辑切换为居中

添加图片注释,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值