RabbitMQ是一种消息队列产品。
顾名思义,消息队列是用来接收和转发消息的队列。消息是二进制数据块,可以是简单的字符串,也可以是更复杂的数据对象。队列接收消息生产者发送的消息,并将消息转发给消息消费者。
消息生产者、消息队列、消息消费者不必在同一服务器上,实际上在大多数应用中,它们确实都不在同一服务器上。
从上面对消息队列的介绍中不难看出,消息队列可以实现程序之间的异步协作。
以12306为例,购买火车票有下单系统处理下单请求、支付系统处理支付、通知系统发送“购票成功”通知。支付后用户就能马上看到支付结果,这时用户就能知道购票成功了,所以发送通知这个步骤不是需要立即执行的。12306可以发送通知命令到消息队列中,通知系统从队列中取出命令并通过邮件和短信给用户发送“购票成功”的通知。这样就实现了业务之间的解耦,减少了系统的压力。
其他常见场景包括最终一致性、广播、错峰流控等等。
市场上的消息队列产品有很多,比如ActiveMQ、RabbitMQ、ZeroMQ、Kafka、 RocketMQ ,连Redis这样的数据库系统也支持消息队列。
RabbitMQ是一个开源的AMQP(Advanced Message Queue)实现,服务器端用Erlang语言编写,支持多种语言,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)。
Publisher表示消息的生产者。
Exchange表示交换器,用来接收生产者发送的消息并将这些消息路由给队列。从图中可以看出,Procuder发布的Message进入了Exchange。Exchange通过Routing key与Queue绑定在一起。通过Routing key, RabbitMQ可以得知应该把这个Message放到哪个Queue里。Exchange分发消息时根据类型的不同分发策略有区别,目前共五种类型:fanout、direct、topic、headers、x-delayed-message。
Binding表示Exchange通过Routing key与Queue的路由关系。
Queue表示消息队列,用来接收保存消息直到发送给消费者。
Connection表示一个TCP的连接,Producer和Consumer都是通过TCP连接到RabbitMQ服务器的。
Channel表示建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过Channel发出去的,不管是发布消息还是消息,都是通过Channel完成。从下面的Demo中可以看到,建立Connection后,接下来就是通过Connection建立Channel。
那么,为什么使用Channel,而不是直接使用TCP连接?
对于OS来说,建立和关闭TCP连接是有代价的,频繁的建立关闭TCP连接对于系统的性能有很大的影响,而且TCP的连接数也有限制,这也限制了系统处理高并发的能力。但是,在TCP连接中建立Channel是没有上述代价的,所以引入了Channel的概念,以复用一条 TCP 连接。
Consumer表示消息消费者。
Virtual Host表示虚拟主机,表示一批交换器、消息队列和相关对象。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
Broker表示消息队列服务器实体。
安装RabbitMQ ,用户名设为panweiwei,密码设为panweiwei。
省略。
新建名为名为testExchanges的Exchanges、名为testQueue的Queue,并将两者绑定,Routing key名为testQueue。结果如下如所示
2.消息生产者类Producer
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
private final static String QUEUE_NAME = "testQueue";
private final static String EXCHANGE_NAME = "testExchanges";
private final static String ROUTING_KEY = "testQueue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建一个到服务器的连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("panweiwei");
factory.setPassword("panweiwei");
factory.setHost("127.0.0.1");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);
// 发布消息到队列中
String message = "Hello World!";
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes());
System.out.println("已发送消息:" + message);
//关闭渠道和连接;
channel.close();
conn.close();
}
}
3.消息消费者类Consumer
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
private final static String QUEUE_NAME = "testQueue";
private final static String EXCHANGE_NAME = "testExchanges";
private final static String ROUTING_KEY = "testQueue";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建一个到服务器的连接
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("panweiwei");
factory.setPassword("panweiwei");
factory.setHost("127.0.0.1");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);
String queueName = channel.queueDeclare().getQueue();
// 绑定队列
channel.queueBind(queueName, EXCHANGE_NAME, ROUTING_KEY);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("收到消息:" + message);
}
};
channel.basicConsume(queueName, true, consumer);
}
}
1.运行 Consumer
先运行 Consumer ,这样当生产者发送消息的时候能在消费者后端看到消息记录。
2.运行 Producer
接着运行 Producer ,发布一条消息,在 Consumer 的控制台能看到接收的消息
收到消息:Hello World!
连续发送多条消息,在 Consumer 的控制台能看到接收的消息
收到消息:Hello World!
收到消息:Hello World!
收到消息:Hello World!
如果没有看到,请看RabbitMQ的管理界面,找到testQueue,看其中有没有消息没有发出去
如图所示,有6条消息没有发出去。
EDN.