RabbitMQ是什么?
RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,它主要是使用Erlang语言进行编写的,并且还基于AMQP协议。
AMQP协议 Advanced MEssage Queueing Protocol 高级消息协议
四大核心概念
生产者
产生数据发送消息的程序是生产者
交换机
交换机是 RabbitMQ的非常重要的一部分,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到指定队列还是推送到多个队列或者是把消息丢弃,这个由交换机类型决定(直连交换机,主题交换机,扇形交换机)。
队列
队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。多个生产者可以将消息发送到一个队列,多个消费者可以尝试从一个队列接收数据。
消费者
消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。生产者消费者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。
RabbitMQ 核心部分
Broker:
接收和分发消息的应用,RabbitMQ Server 就是 Message Broker
Virtual host:
出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似namespace的概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost让每个用户在自己的 vhost下创建 exchange/queue 等
Connection:
publisher/consumer 和 broker 之间的 TCP 连接。
Channel:
如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的资源消耗非常大,效率也较低。
对于上述情况,Channel在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个线程创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客 户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
Exchange:
message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发 消息到 queue 中去。常用的类型有:direct (point-to-point 直连), topic (publish-subscribe 主题) and fanout (multicast 发布订阅模型)
Queue:队列,消息最终被送到这里等待consumer消费
Binding:
exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。
Producer/Consumer
producer 消息生产者
consumer 消息消费者
Queue(消息队列)
消息队列,提供了FIFO的处理机制,具有缓存消息的能力。rabbitmq中,队列消息可以设置为持久化,临时或者自动删除。
设置为持久化的队列,queue中的消息会在server本地硬盘存储一份,防止系统宕机,数据丢失
设置为临时队列,queue中的数据在系统重启之后就会丢失
设置为自动删除的队列,当不存在用户连接到server,队列中的数据会被自动删除
Exchange(交换机)
在rabbitmq中,producer不是通过信道直接将消息发送给queue,而是先发送给Exchange。一个Exchange可以和多个Queue进行绑定,producer在传递消息的时候,会传递一个ROUTING_KEY,Exchange会根据这个ROUTING_KEY按照特定的路由算法,将消息路由给指定的queue。和Queue一样,Exchange也可设置为持久化,临时或者自动删除。
交换机存在4种类型:
Direct :直接交换器,工作方式类似于单播,Exchange会将消息发送完全匹配ROUTING_KEY的Queue
fanout:广播是式交换器,不管消息的ROUTING_KEY设置为什么,Exchange都会将消息转发给所有绑定的Queue。
topic:主题交换器,工作方式类似于组播,Exchange会将消息转发和ROUTING_KEY匹配模式相同的所有队列,比如,ROUTING_KEY为user.stock的Message会转发给绑定匹配模式为:
*.stock,user.stock,
* . *
* #.user.stock.#的队列。( * 表是匹配一个任意词组,#表示匹配0个或多个词组)
headers:消息体的header匹配
一个简单的生产者消费者
生产者代码
public class Producer1 {
public static final String QUEUE_NAME = "TEST_QUEUE";
public static void main(String[] args) {
try {
Channel channel = RabbitMqUtils.getChannel();
/** queueDeclare 参数说明
* 生成一个队列
* 1.队列名称
* 2.durable 队列是否持久化 默认消息存储在内存中
* 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费
* 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除
* 5.其他参数
*/
//设置队列持久化
boolean durable = true;
channel.queueDeclare(QUEUE_NAME,durable,false,false,null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.next();
/**
* 发送一个消息
* 1.发送到那个交换机
* 2.路由的 key 是哪个
* 3.其他的参数信息
* 4.发送消息的消息体
*/
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完成:"+message);
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
消费者代码
public class Consumer1 implements Runnable{
public static final String QUEUE_NAME = "TEST_QUEUE";
private String strNo;
public Consumer1(String strNo){
this.strNo = strNo;
}
public void consumerHandle(){
try {
Channel channel = RabbitMqUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String str = new String(delivery.getBody());
System.out.println(strNo+"接收到的消息:"+str);
};
CancelCallback callback = consumerTag -> {
System.out.println("消费者取消消费接口回调逻辑:"+consumerTag);
};
System.out.println(this.strNo+"消费者启动等待");
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功之后是否要自动应答 true 代表自动应答 false 手动应答 一般不会自动应答
* 3.消息消费的回调
* 4.消费者未成功消费的回调
*/
channel.basicConsume(QUEUE_NAME,true,deliverCallback,callback);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
@Override
public void run() {
consumerHandle();
}
}
轮训分发消息
消费者分别用两个线程启动 C0和C1
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(new Consumer1("C0"));
executor.submit(new Consumer1("C1"));
executor.shutdown();
}
生产者发送消息
启动消费者
两个线程分别间隔收到不同的消息