文章目录
一、认识
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SWerRPXz-1586753363532)(RabiitMQ.assets/image-20200409124054690.png)]
二、安装
1.docker安装
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
[root@localhost ~]# docker logs rabbitmq
Starting RabbitMQ 3.8.3 on Erlang 22.3
Copyright (c) 2007-2020 Pivotal Software, Inc.
Licensed under the MPL 1.1. Website: https://rabbitmq.com
## ## RabbitMQ 3.8.3
## ##
########## Copyright (c) 2007-2020 Pivotal Software, Inc.
###### ##
########## Licensed under the MPL 1.1. Website: https://rabbitmq.com
Doc guides: https://rabbitmq.com/documentation.html
Support: https://rabbitmq.com/contact.html
Tutorials: https://rabbitmq.com/getstarted.html
Monitoring: https://rabbitmq.com/monitoring.html
Logs: <stdout>
Config file(s): /etc/rabbitmq/rabbitmq.conf
Starting broker...2020-02-04 09:50:28.118 [info] <0.278.0>
node : rabbit@372182626d08
home dir : /var/lib/rabbitmq
config file(s) : /etc/rabbitmq/rabbitmq.conf
cookie hash : i+iD3VVt4nHYnHzfhKS/mw==
log(s) : <stdout>
database dir : /var/lib/rabbitmq/mnesia/rabbit@372182626d08
2. 配置文件
[root@localhost lib]$ cat
/var/lib/docker/volumes/8ed25145a2d59a1b7ba4698bd326f2fe291c843e5158a27d30d3bb8a5ae2017c/_data/config/generated/rabbitmq.config
<!--{loopback_users,[]} 打开:guest user from anywhere on the network-->
[{rabbitmq_management,[{tcp_config,[{port,15672}]}]},
{rabbit,[{tcp_listeners,[5672]},{loopback_users,[]}]}].
2.RabbitMQ的简单指令
启动服务:rabbitmq-server -detached【 /usr/local/rabbitmq/sbin/rabbitmq-server -detached 】
重启服务:rabbitmq-server restart
关闭服务:rabbitmqctl stop
查看状态:rabbitmqctl status
列出角色:rabbitmqctl list_users
开启某个插件:rabbitmq-pluginsenable xxx
关闭某个插件:rabbitmq-pluginsdisablexxx
注意:重启服务器后生效。
三、分类
1. Direct直连
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJrXEhm3-1586753363535)(RabiitMQ.assets/image-20200409125445036.png)]
P:生产者,发送消息
C:消费者,等待消息
queue:消息队列,类似邮箱
- 依赖
<!--rabbitMQ-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
- 代码示例1:
@Test
public void sendMessage() throws IOException, TimeoutException {
//创建连接mq的连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.56.15");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/dev");
connectionFactory.setUsername("dev");
connectionFactory.setPassword("dev");
//获取链接对象
Connection connection = connectionFactory.newConnection();
//获取链接通道对象
Channel channel = connection.createChannel();
//通道绑定消息队列
//参数1:队列不存在自动创建
//参数2:durable是否持久化到磁盘,即mq关闭后,数据会持久化到磁盘,false则关闭后删除
//参数3:exclusive是否独占队列,false指其他连接也可以通过该队列
// 参数4:autoDelete是否消费后立即删除队列,false不自动删除
// 参数5:额外参数
channel.queueDeclare("hello", false, false, false, null);
//发布消息
//参数1:交换机 参数2:routingKey队列名称 参数3:额外设置 参数4:消息内容
channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes());
channel.close();
connection.close();
}
- 问题:
- 用户名密码错误
com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.
at com.rabbitmq.client.impl.AMQConnection.start(AMQConnection.java:384)
- 代码示例2:
public class RabiitMqTest {
//创建连接mq的连接工厂对象
public static ConnectionFactory connectionFactory;
static {
connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.56.15");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
}
private static Connection createConnection() {
try {
//获取链接对象
return connectionFactory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private void closeConnection(Connection connection,Channel channel){
try {
if(channel != null) channel.close();
if(connection != null)connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
@Test
public void publisherSendMessage() throws IOException {
Connection connection = createConnection();
//获取链接通道对象
Channel channel = connection.createChannel();
//通道绑定消息队列
//参数1:队列不存在自动创建
//参数2:durable是否持久化到磁盘,即mq关闭后,数据会持久化到磁盘,false则关闭后删除队列,队列中未消费的消息将会丢失
//参数3:exclusive是否独占队列,false指其他连接也可以通过该队列
// 参数4:autoDelete是否消费者消费且关闭后立即删除队列,false不自动删除
// 参数5:额外参数
channel.queueDeclare("hai", true, false, true, null);
//发布消息
//参数1:交换机 参数2:routingKey队列名称 参数3:额外设置(持久化消息,关闭重启后消息不会消失) 参数4:消息内容
channel.basicPublish("", "hai", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello rabbitmq".getBytes());
closeConnection(connection,channel);
}
public static void main(String[] args) throws IOException {
Connection connection = createConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hai", true, false, true, null);
//消费消息
//参数1:消费队列名称
//参数2:开始消费自动确认机制
//参数3:消费消息时回调接口
channel.basicConsume("hai",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("==================" + body);
}
});
}
}
2. Work queue
也称Task queues,任务模型。消息处理比较耗时时,生产消息远大于消费消息,会导致消息堆积。Work queue 模式:多个消费者绑定一个队列共同消费队列消息,消息一旦消费即消失,不会重复执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxKklJne-1586753363536)(RabiitMQ.assets/image-20200410131040607.png)]
P:生产者,发送消息
C1、C2:消费者,消费消息
默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均每个消费者将得到相同数量的消息。这种分发消息的方式称为循环。在三个或更多的工人中尝试这种方法。
即使一个消费者消费的很慢,也是平均分配,消息循环分配到消费者的队列,等待消费者消费。
- 代码1:略
两个消费者消费同一个队列
- 代码2:手动确认
package rabbitmq;
import com.rabbitmq.client.*;
import org.junit.Test;
import java.io.IOException;
public class WorkQueueConsumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMqUtil.createConnection();
Channel channel = connection.createChannel();
channel.basicQos(1);//每次消费通道只能消费一个消息
channel.queueDeclare("workQueue", true, false, false, null);
//消费消息
//参数1:消费队列名称
//参数2:开始消费自动确认机制
//参数3:消费消息时回调接口
channel.basicConsume("workQueue",false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("==================消费者2" + new String(body));
//手动确认消息 参数1:确认队列中具体的消息 参数2:是否开启多个消息同时确认
channel.basicAck(envelope.getDeliveryTag(),false);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
3. Fanout
广播 putting it all together
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ES8poDRB-1586753363537)(RabiitMQ.assets/image-20200410142817530.png)]
可以有多个消费者
每个消费者有自己的队列,每个队列绑定到交换机
生产者发送的消息到交换机,交换机决定发给哪个队列,生产者无法决定
交换机把消息发送给绑定过的所有队列,临时队列,消费者消费,队列删除。
队列的消费者都能拿到消息,实现一条消息多个消费者消费。
- 代码示例1:
publisher
package rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import static rabbitmq.RabbitMqUtil.createConnection;
public class FanoutPublisher {
public static void main(String[] args) throws IOException {
Connection connection = createConnection();
//获取链接通道对象
Channel channel = connection.createChannel();
//参数1:交换机名称 参数2:交换机type
channel.exchangeDeclare("logs","fanout");
channel.basicPublish("logs","",null,"fanout type".getBytes());
RabbitMqUtil.closeConnection(connection,channel);
}
}
consumer1:
package rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import static rabbitmq.RabbitMqUtil.createConnection;
public class FanoutConsumer2 {
public static void main(String[] args) throws IOException {
Connection connection = createConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("logs","fanout");
//临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机 参数1:队列名,参数2:交换机名,参数3:routingKey
channel.queueBind(queue,"logs","");
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者3:=====" + new String(body));
}
});
}
}
consumer2、 consumer3同上
4. Routing
4.1 Direct(订阅)
不同消息被不同的队列消费
-
队列与交换机的绑定,要指定一个RoutingKey
-
向Exchange发送消息时,必须指定RoutingKey
-
Exchange根据RoutingKey判断消息发送
![image-20200412222236932](RabiitMQ.assets/image-20200412222236932.png
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZYJ9HOg-1586753363538)(RabiitMQ.assets/image-20200412222406467.png)]
-
代码示例1
publisher
package rabbitmq; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.MessageProperties; import java.io.IOException; import static rabbitmq.RabbitMqUtil.createConnection; public class RoutingDirectPublisher { public static void main(String[] args) throws IOException { Connection connection = createConnection(); //获取链接通道对象 Channel channel = connection.createChannel(); //参数1:交换机名称 参数2:交换机type String exchangeName = "logs_direct"; channel.exchangeDeclare(exchangeName,"direct"); String routingKey = "error"; channel.basicPublish(exchangeName,routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN,("基于direct模式的【"+ routingKey+"】的交换机发送消息:").getBytes()); RabbitMqUtil.closeConnection(connection,channel); } }
consumer1:
package rabbitmq; import com.rabbitmq.client.*; import java.io.IOException; import static rabbitmq.RabbitMqUtil.createConnection; public class RoutingDirectConsumer1 { public static void main(String[] args) throws IOException { Connection connection = createConnection(); Channel channel = connection.createChannel(); String exchangeName = "logs_direct"; channel.exchangeDeclare(exchangeName,"direct"); //临时队列 String queue = channel.queueDeclare().getQueue(); //绑定交换机 参数1:队列名,参数2:交换机名,参数3:routingKey channel.queueBind(queue,exchangeName,"error"); channel.queueBind(queue,exchangeName,"info"); channel.queueBind(queue,exchangeName,"warning"); channel.basicConsume(queue,true,new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("消费者1:=====" + new String(body)); } }); } }
consumer2
package rabbitmq; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.Exchanger; import static rabbitmq.RabbitMqUtil.createConnection; public class RoutingDirectConsumer2 { public static void main(String[] args) throws IOException { Connection connection = createConnection(); Channel channel = connection.createChannel(); String exchangeName = "logs_direct"; channel.exchangeDeclare(exchangeName,"direct"); //临时队列 String queue = channel.queueDeclare().getQueue(); //绑定交换机 参数1:队列名,参数2:交换机名,参数3:routingKey channel.queueBind(queue,exchangeName,"error"); channel.basicConsume(queue,true,new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); System.out.println("消费者2:=====" + new String(body)); } }); } }
4.2 Topic
可以根据RoutingKey把消息路由到不同的队列,可以使用通配符。
多个单词之间以"."分割,例如:item.insert
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ak2MgVQ6-1586753363539)(RabiitMQ.assets/image-20200412230317294.png)]
-
通配符
* (star) can substitute for exactly one word. 匹配不多不少1个单词
# (hash) can substitute for zero or more words. 匹配一个或多个单词
-
代码示例1
publisher
package rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import static rabbitmq.RabbitMqUtil.createConnection;
public class RoutingTopicPublisher {
public static void main(String[] args) throws IOException {
Connection connection = createConnection();
//获取链接通道对象
Channel channel = connection.createChannel();
//参数1:交换机名称 参数2:交换机type
String exchangeName = "logs_topic";
channel.exchangeDeclare(exchangeName,"topic");
// String routingKey = "user.#";
String routingKey = "user.save.info";
channel.basicPublish(exchangeName,routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN,("基于direct模式的【"+ routingKey+"】的交换机发送消息:").getBytes());
RabbitMqUtil.closeConnection(connection,channel);
}
}
consumer1
package rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import static rabbitmq.RabbitMqUtil.createConnection;
public class RoutingTopicConsumer2 {
public static void main(String[] args) throws IOException {
Connection connection = createConnection();
Channel channel = connection.createChannel();
String exchangeName = "logs_topic";
channel.exchangeDeclare(exchangeName,"topic");
//临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机 参数1:队列名,参数2:交换机名,参数3:routingKey
channel.queueBind(queue,exchangeName,"#");
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2:=====" + new String(body));
}
});
}
}
consumer2:
package rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import static rabbitmq.RabbitMqUtil.createConnection;
public class RoutingTopicConsumer1 {
public static void main(String[] args) throws IOException {
Connection connection = createConnection();
Channel channel = connection.createChannel();
String exchangeName = "logs_topic";
channel.exchangeDeclare(exchangeName,"topic");
//临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机 参数1:队列名,参数2:交换机名,参数3:routingKey
channel.queueBind(queue,exchangeName,"user.*");
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1:=====" + new String(body));
}
});
}
}
四、SpringBoot整合RabbitMQ
SpringBoot支持AMQP协议
- 依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.13.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
spring:
application:
name: rabbitmq-springboot
rabbitmq:
host: 192.168.56.15
port: 5672
username: guest
password: guest
virtual-host: /
- 代码示例1
publisher
package springboot.rabbitmq;
import com.springboot.rabbitmq.RabbitMqSpringBootApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = RabbitMqSpringBootApplication.class)
@RunWith(SpringRunner.class)
public class Publisher {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void direct() {
rabbitTemplate.convertAndSend("direct", "hello direct");
}
@Test
public void work() {
for (int i = 0; i < 10; i++) {
//springboot work模式默认均分
rabbitTemplate.convertAndSend("work", "hello work queue" + i);
}
}
@Test
public void fanout() {
rabbitTemplate.convertAndSend("fanoutLog","", "hello fanout" );
}
@Test
public void routingDirect() {
rabbitTemplate.convertAndSend("user","user.info", "hello fanout" );
}
@Test
public void routingTopic() {
rabbitTemplate.convertAndSend("product","pro.info", "hello fanout" );
}
}
DirectConsumer
package com.springboot.rabbitmq.consumer;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
//默认 持久化,非独占,不自动删除
@RabbitListener(queuesToDeclare = @Queue(value = "direct"))
public class DirectConsumer {
@RabbitHandler
public void receive(String message){
System.out.println("===message===" + message);
}
}
consumer
@RabbitListener 可以写在方法上,此时不用使用@RabbitHandler
package com.springboot.rabbitmq.consumer;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@RabbitListener(queuesToDeclare = @Queue("work"))
public void workQueue1(String message) {
System.out.println("===message1===" + message);
}
@RabbitListener(queuesToDeclare = @Queue("work"))
public void workQueue2(String message) {
System.out.println("===message2===" + message);
}
@RabbitListener(bindings = {
@QueueBinding(value = @Queue,//不声明名称即创建临时队列
exchange = @Exchange(value = "fanoutLog", type = "fanout"))
})
public void fanout1(String message) {
System.out.println("===message1===" + message);
}
@RabbitListener(bindings = {
@QueueBinding(value = @Queue,//不声明名称即创建临时队列
exchange = @Exchange(value = "fanoutLog", type = "fanout"))
})
public void fanout2(String message) {
System.out.println("===message2===" + message);
}
@RabbitListener(bindings = {
@QueueBinding(value = @Queue,
exchange = @Exchange(value = "user", type = "direct"),
key = {"user.error"})
})
public void routingDirect(String message) {
System.out.println("===message1===" + message);
}
@RabbitListener(bindings = {
@QueueBinding(value = @Queue,
exchange = @Exchange(value = "user", type = "direct"),
key = {"user.info"})
})
public void routingDirect2(String message) {
System.out.println("===message2===" + message);
}
@RabbitListener(bindings = {
@QueueBinding(value = @Queue,
exchange = @Exchange(value = "product", type = "topic"),
key = {"pro.*"})
})
public void routingTopic(String message) {
System.out.println("===message1===" + message);
}
@RabbitListener(bindings = {
@QueueBinding(value = @Queue,
exchange = @Exchange(value = "product", type = "topic"),
key = {"#"})
})
public void routingTopic2(String message) {
System.out.println("===message2===" + message);
}
}