目录
2.3 RabbitMQ在macOS 和 Windows下的安装
一、RabbitMQ初识
1.1 介绍
1. 初识 RabbitMQ
2. RabbitMQ 的安装和启动
3. RabbitMQ 管理后台
4. 实战案例演示
5. 交换机工作模式(类似于计算机网络中 路由器下一层级硬件)
6. Spring Boot 整合 RabbitMQ
1.2 初识消息队列
核心思想:
接收并转发消息。你可以把它想象成一个邮局,邮筒、信件,我们把信件放进邮筒就可以走了,我们非常确定邮筒会经过邮递员叔叔派送,送到收件人手中。
RabbitMQ就包含邮局、邮筒、邮递员。我们只要把消息给到RabbitMQ,剩下的就不必操心。我们就可以认为,消息最终会交到我们希望的收件人手中。
producer:
生产者、消息发送者
queue:
邮筒,由RabbitMQ管理。
consumer:
消费者、收件人
三者可以不在一台机器上,实现解耦。
消息队列:
Message Queue:
enqueue、dequeue
特性:
容灾(消息可以持久化)
性能
为什么要用消息队列:
系统解耦(避免子系统之间相互调用接口)
异步调用
流量削峰(把海量请求先存储在队列中,再以我们能接受的频率发送给我们)
1.3 暂无
1.4 暂无
1.5 RabbitMQ 的特点和核心概念
开源、跨语言
Erlang语言编写:
交换机上常用语言、性能很好、通信方面有优势,和原生Socket有一样低的延迟。节点与节点之间数据传输和数据复制,性能很好。MQ技术选型指标,数据复制延迟低不低。数据复制延迟低,可以承受流量高峰期压力。
应用广泛
社区活跃、API丰富
AMQP协议
advanced message queuing protocol
专门面向消息中间件的一种开放式标准应用层协议
RabbitMQ核心概念
Server: 服务
connection: 与Server建立连接
channel: 信道。connection里面有多个channel。几乎所有的操作都在信道上进行,客户端可以建立多个信道
message: 消息。由properties(对消息进行额外修饰,优先级、延迟、自定义属性)和body组成
virtual host: 虚拟主机,顶层隔离。同一个虚拟主机下,不能有重复的交换机和queue
Exchange: 交换机,接收生产者的消息的。然后根据指定的路由器去把消息转发到所绑定的队列上。
binding: 绑定交换与队列
routing key: 路由键,路由规则,虚拟机可以用它来确定这个消息如何进行一个路由
queue: 队列,消费者只需要监听队列来消费 信息,不需要关注消息来自于哪个Exchange
Exchange和message queue存在着绑定的关系,一个exchange可以绑定多个消息队列
消息流转过程
二、RabbitMQ的安装
2.1 RabbitMQ在CentOS下的安装 part1
安装Erlang
安装erlang-rpm包,该包经过RabbitMQ官方处理
RabbitMQ版本与Erlang版本有对应关系
vim /etc/yum.repos.d/rabbitmq_erlang.repo
[rabbitmq-erlang]
name=rabbitmq-erlang
baseurl=https://dl.bintray.com/rabbitmq-erlang/rpm/erlang/22/el/7
gpgcheck=1
gpgkey=https://dl.bintray.com/rabbitmq/Keys/rabbitmq-release-signing-key.asc
repo_gpgcheck=0
enabled=1
yum clear all
yum makecache
yum install erlang
要确认源是“rabbitmq_erlang”!!
下载的包名类似这样:erlang-22.3-1.e17.x86_64.rpm
检测erlang语言是否安装成功:erl -version
2.2 RabbitMQ在CentOS下的安装 part2
//导入密钥
rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
//下载安装包
wget https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.8.2/rabbitmq-server-3.8.2-1.el7.noarch.rpm
//如果速度比较慢,就用:
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.2/rabbitmq-server-3.8.2-1.el7.noarch.rpm
//下载完成后,安装:
yum install rabbitmq-server-3.8.2-1.el7.noarch.rpm
//如果出现解压错误,说明下载了多次,用ls -la看一下有几个文件,如果有多个安装包,要把多余的删掉,把正确的改名为rabbitmq-server-3.8.2-1.el7.noarch.rpm,再执行yum install来安装
到这里RabbitMQ就安装好了
RabbitMQ常用命令
启动RabbitMQ:
systemctl start rabbitmq-server
看看端口有没有起来,查看状态:
rabbitmqctl status
开启web管理界面
rabbitmq-plugins enable rabbitmq_management
停止RabbitMQ
rabbitmqctl stop
设置开机启动
systemctl enable rabbitmq-server
要检查RabbitMQ服务器的状态,请运行:
systemctl status rabbitmq-server
2.3 RabbitMQ在macOS 和 Windows下的安装
上传在CSDN资源中。
三、RabbitMQ的应用
3.1 RabbitMQ的管理后台
开启web管理界面
rabbitmq-plugins enable rabbitmq_management
创建用户名和密码
rabbitmqctl add_user admin password
给用户设置权限
rabbitmqctl set_user_tags admin administractor
访问浏览器进入管理页面
http://127.0.0.1:15672
管理后台功能
概览页面
添加用户
创建虚拟主机(Virtual Hosts)
3.2 第一个生产者
1. IDEA新建Maven项目
2. 引入依赖
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
//要求记录日记
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.29</version>
</dependency>
</dependencies>
3. 给RabbitMQ新建admin用户,把“/” Hosts的权限给admin用户一个。远端/代码 就可以用admin账户登录了。
4. 代码如下:
/**
* 描述: Hello World 的发送类,连接到RabbitMQ服务端,然后发送一条消息,然后退出。
*/
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置RabbitMQ地址
factory.setHost("114.55.219.216");
factory.setUsername("admin");
factory.setPassword("password");
//建立连接
Connection connection = factory.newConnection();
//获得信道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//发布消息
String message = "Hello World! 2";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
System.out.println("发送了消息:" + message);
//关闭连接
channel.close();
connection.close();
}
}
3.3 第一个消费者
1. 代码如下:
/**
* 描述: 接收消息,并打印,持续运行
*/
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置RabbitMQ地址
factory.setHost("114.55.219.216");
factory.setUsername("admin");
factory.setPassword("password");
//建立连接
Connection connection = factory.newConnection();
//获得信道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//接收消息并消费
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息:" + message);
}
});
}
}
3.4 暂无
3.5 根据消息内容作处理
/**
* 描述: 消费者,接收前面的批量消息
*/
public class Worker {
private final static String TASK_QUEUE_NAME = "task_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置RabbitMQ地址
factory.setHost("localhost");
//建立连接
Connection connection = factory.newConnection();
//获得信道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
System.out.println("开始接收消息");
//有助于平均压力
channel.basicQos(1);
channel.basicConsume(TASK_QUEUE_NAME, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到了消息:" + message);
try {
doWork(message);
} finally {
System.out.println("消息处理完成");
//手动消息确认。处理完了。手动消息确认 参数中自动确认消息 一定要有一个。
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
});
}
private static void doWork(String task) {
char[] chars = task.toCharArray();
for (char ch : chars) {
if (ch == '.') {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
3.6 多个消费者平均压力
多个消费者:
循环调度(按顺序你一个他一个,一次分完。未考虑各个任务压力大小区别)
公平派遣(根据消费者压力决定是不是派给你)
消息确认(消费者告诉MQ我处理完了,来下一个吧)
代码 加在上面代码块中
四、交换机类型
4.1 交换机类型 - fanout
交换机工作模式:
fanout: 广播,这种模式只需要将队列绑定到交换机上即可,是不需要设置路由键的。只要是和此交换机做关联的queue,一律都会收到消息。
direct: 根据RoutingKey匹配 消息 路由到指定的队列。
topic: 生产者指定RoutingKey,消息 根据消费端指定的队列 通过模糊匹配的方式 进行相应转发。
headers: 根据发送消息内容中的headers属性来匹配。
fanout模式代码:
/**
* 描述: 发送日志信息
*/
public class EmitLog {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
String message = "info: Hello World!";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
System.out.println("发送了消息:" + message);
channel.close();
connection.close();
}
}
/**
* 描述: 接收日志消息
*/
public class ReceiveLogs {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
//非持久 会自动删除的队列
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("开始接收消息");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息:" + message);
}
};
channel.basicConsume(queueName, true, consumer);
}
}
4.2 暂无
4.3 交换机类型 - direct
/**
* 描述: direct类型的交换机,发送消息
*/
public class EmitLogDirect {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String message = "info:Hello World!";
channel.basicPublish(EXCHANGE_NAME, "info", null, message.getBytes("UTF-8"));
System.out.println("发送了消息," + "等级为info,消息内容:" + message);
message = "warning:Hello World!";
channel.basicPublish(EXCHANGE_NAME, "warning", null, message.getBytes("UTF-8"));
System.out.println("发送了消息," + "等级为warning,消息内容:" + message);
message = "error:Hello World!";
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes("UTF-8"));
System.out.println("发送了消息," + "等级为error,消息内容:" + message);
channel.close();
connection.close();
}
}
/**
* 描述: 接收3个等级的日志
*/
public class ReceiveLogsDirect1 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//生成一个随机的临时的queue
String queueName = channel.queueDeclare().getQueue();
//一个交换机同时绑定3个queue 重点
channel.queueBind(queueName, EXCHANGE_NAME, "info");
channel.queueBind(queueName, EXCHANGE_NAME, "warning");
channel.queueBind(queueName, EXCHANGE_NAME, "error");
System.out.println("开始接收消息");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息:" + message);
}
};
channel.basicConsume(queueName, true, consumer);
}
}
/**
* 描述: 接收1个等级的日志
*/
public class ReceiveLogsDirect2 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//生成一个随机的临时的queue
String queueName = channel.queueDeclare().getQueue();
//一个交换机绑定1个queue 重点
channel.queueBind(queueName, EXCHANGE_NAME, "error");
System.out.println("开始接收消息");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息:" + message);
}
};
channel.basicConsume(queueName, true, consumer);
}
}
4.4 交换机类型 - topic生产者
topic模式:
* 可以代替一个单词
# 可以代替零个或多个单词
/**
* 描述: topic模式交换机,发送消息
*/
public class EmitLogTopic {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String message = "Animal World";
String[] routingKeys = new String[9];
routingKeys[0] = "quick.orange.rabbit";
routingKeys[1] = "lazy.orange.elephant";
routingKeys[2] = "quick.orange.fox";
routingKeys[3] = "lazy.brown.fox";
routingKeys[4] = "lazy.pink.rabbit";
routingKeys[5] = "quick.brown.fox";
routingKeys[6] = "orange";
routingKeys[7] = "quick.orange.male.rabbit";
routingKeys[8] = "lazy.orange.male.rabbit";
for (int i = 0; i < routingKeys.length; i++) {
channel.basicPublish(EXCHANGE_NAME, routingKeys[i], null, message.getBytes("UTF-8"));
System.out.println("发送了:" + message+" routingKey:"+routingKeys[i]);
}
channel.close();
connection.close();
}
}
4.5 交换机类型 - topic 消费者
/**
* 描述: 特定路由键
*/
public class ReceiveLogsTopic1 {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//生成一个随机的临时的queue
String queueName = channel.queueDeclare().getQueue();
//重点 交换机给满足 类似正则规则的queue 发送消息,消费者再消费此queue中的消息
String routingKey = "*.orange.*";
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
System.out.println("开始接收消息");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息:" + message + " routingKey: " + envelope.getRoutingKey());
}
};
channel.basicConsume(queueName, true, consumer);
}
}
/**
* 描述: 特定路由键
*/
public class ReceiveLogsTopic2 {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//生成一个随机的临时的queue
String queueName = channel.queueDeclare().getQueue();
//重点 交换机给满足 类似正则规则的queue 发送消息,消费者再消费此queue中的消息
String routingKey = "*.*.rabbit";
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
//重点 交换机给满足 类似正则规则的queue 发送消息,消费者再消费此queue中的消息
String routingKey2 = "lazy.#";
channel.queueBind(queueName, EXCHANGE_NAME, routingKey2);
System.out.println("开始接收消息");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息:" + message + " routingKey: " + envelope.getRoutingKey());
}
};
channel.basicConsume(queueName, true, consumer);
}
}
五、SpringBoot与RabbitMQ的整合
5.1 SpringBoot整合RabbitMQ
Spring initiallzr -> 可选依赖 -> finished -> add as maven project
//修改版本号为2.2.1
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
//加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
/**
* 描述: rabbitmq配置类
*/
@Configuration
public class TopicRabbitConfig {
@Bean
public Queue queue1() {
return new Queue("queue1");
}
@Bean
public Queue queue2() {
return new Queue("queue2");
}
@Bean
TopicExchange exchange() {
return new TopicExchange("bootExchange");
}
@Bean
Binding bingdingExchangeMessage1(Queue queue1, TopicExchange exchange) {
return BindingBuilder.bind(queue1).to(exchange).with("dog.red");
}
@Bean
Binding bingdingExchangeMessage2(Queue queue2, TopicExchange exchange) {
return BindingBuilder.bind(queue2).to(exchange).with("dog.#");
}
}
/**
* 描述: 发送消息
*/
@Component
public class MsgSender {
@Autowired
private AmqpTemplate rabbitmqTemplate;
public void send1() {
String message = "This is message 1, routing key is dog.red";
System.out.println("发送了:"+message);
this.rabbitmqTemplate.convertAndSend("bootExchange", "dog.red", message);
}
public void send2() {
String message = "This is message 2, routing key is dog.black";
System.out.println("发送了:"+message);
this.rabbitmqTemplate.convertAndSend("bootExchange", "dog.black", message);
}
}
@SpringBootTest
public class SpringBootRabbitmqProducerApplicationTests {
@Autowired
MsgSender msgSender;
@Test
public void send1() {
msgSender.send1();
}
@Test
public void send2(){
msgSender.send2();
}
}
/**
* 描述: 消费者1
*/
@Component
@RabbitListener(queues = "queue1")
public class Receiver1 {
@RabbitHandler
public void process(String message) {
System.out.println("Receiver1: " + message);
}
}