RabbitMQ简介
官方定义:RabbitMQ是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ配置的转发机制接收服务端发来的消息。RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。RabbitMQ是使用 Erlang语言来编写的,并且RabbitMQ是基于AMQP协议的
RabbitMQ整体架构:
RabbitMQ核心概念:
Server:又称Broker,接受客户端的连接,实现AMQP实体服务
Connection:连接,应用程序与Broker的网络连接
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道。客户端可建立多个Channel,每个Channel代表一个会话任务
Message:消息,服务器和应用程序之间传送的数据,由Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body则是消息体的内容
Virtual host:虚拟地址,用于进行逻辑隔离,最上层的消息路由。同一个Virtual Host里面不能有相同名称的Exchange或Queue
Exchange:交换机,接收消息,根据路由键转发消息到绑定的队列
Binding:Exchange和Queue之间的虚拟连接,binding中可以包含routing key
Routing key:一个路由规则,虚拟机可用它确定如何路由一个特定消息
Queue:也称为Message Queue,消息队列,保存消息并将它们转发给消费者
MQ的优点:
1、异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。
2、应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。
3、流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求,一般在秒杀活动中应用广泛 。
发送消息的过程
1、生产者和Broker通过TCP连接建立通道。
2、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
3、Exchange通过Routing Key将消息转发到指定的Queue(队列)
接收消息的过程
1、生产者和Broker通过TCP连接建立通道。
2、消费者监听指定的Queue(队列)
3、当有消息到达Queue时Broker默认将消息推送给消费者。
4、消费者接收到消息。
RabbitMQ入门
环境:SpringBoot+RabbitMQ 5.8.0
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
</dependencies>
创建消息生产者,不指定交换机
public class ProducerDemo {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String exchangeName = "";
String routingKey= "defaultQueue";
String data = "hello";
//通过通道发送信息,第三个参数是消息属性,可以配置消息的过期时间
channel.basicPublish(exchangeName,routingKey,null,data.getBytes());
//关闭连接
channel.close();
connection.close();
}
}
创建消息消费者,不指定交换机
public class ConsumerDemo {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String queueName = "defaultQueue";
/**
* 配置监听的队列
* 第一个参数 队列名
* 第二个参数 durable 是否持久化
* 第三个参数 exclusive 是否独占的 相当于加了一把锁
* 第四个参数 autoDelete 是否自动删除
* 第五个参数 map类型,表示对队列中的消息进行详细设置,例如绑定死信队列
*/
channel.queueDeclare(queueName,true,false,false,null);
//创建消费者
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);
}
};
//对消息作出应答,第二个参数表示自动ACK
channel.basicConsume(queueName,true,consumer);
}
}
Direct Exchange(直连交换机)
direct为默认的交换器类型,如果路由键匹配的话,消息就投递到相应的队列。有一个需要注意的地方:如果找不到指定的exchange,就会报错。但routing key找不到的话,不会报错,这条消息会直接丢失,所以此处要小心。
大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键 routing key 。
然后当一个消息携带着路由键为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。
创建消息生产者,使用Direct Exchange
public class PeoducerDirectExchange {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String exchangeName = "directExchange";
String exchangeType = "direct";
String routingKey = "directKey";
String data = "hello";
//声明交换机
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//指定routingKey
channel.basicPublish(exchangeName,routingKey,null,data.getBytes());
channel.close();
connection.close();
}
}
创建消息消费者,使用Direct Exchange
public class ConsumerDirectExchange {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String exchangeName = "directExchange";
String exchangeType = "direct";
String queueName = "directQueue";
String routingKey = "directKey";
//声明一个direct类型的交换机
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//声明一个队列
channel.queueDeclare(queueName,true,false,false,null);
//建立绑定关系,所有发送到Direct Exchange的消息被转发到RouteKey指定的Queue
channel.queueBind(queueName,exchangeName,routingKey);
//创建消费者
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);
}
};
channel.basicConsume(queueName,true,consumer);
}
}
Topic Exchange(主题交换机)
这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。
创建消息生产者,使用Topic Exchange
public class ProducerTopicExchange {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
//使用exchangeName交换器
String exchangeName = "topicExchange";
String exchangeType = "topic";
//指定routingKey
String routingKey1 = "user.topicKey1";
String routingKey2 = "user.topicKey2";
String routingKey3 = "user.topicKey3";
//声明一个topic类型的交换机
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//这里发送三条消息,指定三个routingKey
channel.basicPublish(exchangeName,routingKey1,null,"hello1".getBytes());
channel.basicPublish(exchangeName,routingKey2,null,"hello2".getBytes());
channel.basicPublish(exchangeName,routingKey3,null,"hello3".getBytes());
channel.close();
connection.close();
}
}
channel.basicPublish(exchangeName,routingKey3,null,"hello3".getBytes());
channel.close();
connection.close();
}
}
创建消息消费者,使用Topic Exchange
public class ConsumerTopicExchange {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String exchangeName = "topicExchange";
String exchangeType = "topic";
String queueName = "topicQueue";
//这里routingKey使用模糊匹配
String routingKey = "user.#";
//声明一个topic类型的交换机
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//声明一个队列
channel.queueDeclare(queueName,true,false,false,null);
//建立绑定关系
channel.queueBind(queueName,exchangeName,routingKey);
//创建消费者
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);
}
};
channel.basicConsume(queueName,true,consumer);
}
}
routingKey中的通配符
符号* 用来表示一个单词.
符号# 用来表示任意数量(零个或多个)单词
例如:user.* 可以匹配 user.topicKey1,user.topicKey2
user.#可以匹配user.topicKey1, user.topicKey1.add
Fanout Exchange(扇型交换机)
扇形交换机把消息发送给它所知道的所有队列,不处理路由键,所以Fanout交换机转发消息是最快的
创建消息生产者,使用Fanout Exchange
public class ProducerFanoutExchange {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String exchangeName = "fanoutExchange";
String exchangType = "fanout";
//不需要指定路由key
String routingKey = "";
String data = "hello";
//声明一个fanout类型的交换机
channel.exchangeDeclare(exchangeName,exchangType,true,false,false,null);
channel.basicPublish(exchangeName,routingKey,null,data.getBytes());
channel.close();
connection.close();
}
}
创建消息消费者,使用Fanout Exchange
public class ConsumerFanoutExchange1 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String exchangeName = "fanoutExchange";
String exchangeType = "fanout";
String queueName = "fanoutQueue1";
//不需要指定路由key
String routingKey = "";
//声明一个fanout类型的交换机
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//声明一个队列
channel.queueDeclare(queueName,true,false,false,null);
//建立绑定关系
channel.queueBind(queueName,exchangeName,routingKey);
//创建消费者
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);
}
};
channel.basicConsume(queueName,true,consumer);
}
}
创建消息生产者2
public class ConsumerFanoutExchange2 {
public static void main(String[] args) throws IOException, TimeoutException {
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//通过工厂创建连接
Connection connection = connectionFactory.newConnection();
//通过连接创建通道
Channel channel = connection.createChannel();
String exchangeName = "fanoutExchange";
String exchangeType = "fanout";
String queueName = "fanoutQueue2";
//不需要指定路由key
String routingKey = "";
//声明一个fanout类型的交换机
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
//声明一个队列
channel.queueDeclare(queueName,true,false,false,null);
//建立绑定关系
channel.queueBind(queueName,exchangeName,routingKey);
//创建消费者
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);
}
};
channel.basicConsume(queueName,true,consumer);
}
}
注意:1、如果交换机、队列属性发生改变,需要去控制台将之前的交换机、队列删除,才能重新启动成功
2、上面所有的例子都是持久化消息,先启动生产者,消息会被保存到消息队列,登消费者上线可以进行消费。消息队列中必须要有交换机和队列的绑定信息才能被持久化。
项目代码:项目代码
相关阅读:
SpringBoot整合RabbitMQ
RabbitMQ的消息确认机制,消息重试机制
RabbitMQ的死信队列与延时队列