目录
RabbitMQ
MQ应用场景
基本介绍
AMQP协议
RabbitMQ的特性
工作模型
三种主要的交换机
Direct Exchange直连交换机
Topic Exchange主题交换机
Fanout Exchange广播交换机
Java API编程
创建maven工程,pom.xml引入依赖
生产者
消费者
参数说明
进阶支持
TTL(Time To Live)
消息的过期时间
队列的过期时间
死信队列
优先级队列
延迟队列
RPC
服务端流控(Flow Control)
消费端限流
Windows安装步骤
Linux安装步骤
官网文章中文翻译系列
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可以传递消息,并不受客户端/中间件同产品、不同的开发语言等条件限制。
AMQP的实现有:RabbitMQ、OpenAMQ、Apache Qpid等等
RabbitMQ使用Erlang语言编写,使用Mnesia数据库存储消息。
概念 | 解释 |
Broker | 即RabbitMQ的实体服务器。提供一种传输服务,维护一条从生产者到消费者的传输线路,保证消息数据能按照制定的方式传输 |
Exchange | 消息交换机。指定消息按照什么规则路由到哪个队列Queue。 |
Queue | 消息队列。消息的载体,每条消息都会被投送到一个或多个队列中。 |
Binding | 绑定。作用就是将Exchange和Queue按照某种路由规则绑定起来 |
Routing Key | 路由关键字。Exchange根据Routing Key进行消息投递。定义绑定时指定的关键字称为Binding Key。 |
Vhost | 虚拟主机。一个Broker可以有多个虚拟主机,用作不同用户的权限分离。一个虚拟主机持有一组Exchange、Queue和Binding。 |
Producer | 消息生产者。主要讲消息投递到对应的Exchange上面。一般是独立的程序。 |
Consumer | 消息消费者。消息的接收者,一般是独立的程序。 |
Connection | Producer和Consumer与Broker之间的TCP长连接。 |
Channel | 消息通道,也称信道。在客户端的每个连接里可以建立多个Channel,每个Channel代表一个会话任务。在RabbitMQ Java Client API中,channel上定义了大量的编程接口。 |
定义:直连类型的交换机与一个队列绑定时,需要指定一个明确的binding key。
路由规则:发送消息到直连类型的交换机时,只有routing key跟binding key完全匹配时,绑定的队列才能收到消息。
例:
//只有队列1能收到消息
channel.basicPublish("MY_DIRECT_EXCHANGE","key1",null,msg.getBytes());
定义:主题类型的交换机与一个队列绑定时,可以指定按照模式匹配的routing key。
通配符有两个:'*'代表匹配一个单词;'#'代表匹配零个或者多个单词。单词与单词之间用'.'隔开。
路由规则:发送消息到主题类型的交换机时,routing key符合binding key的模式时,绑定的队列才能收到消息。
例:
// 只有队列1能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "sh.abc", null, msg.getBytes());
// 队列2和队列3能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "bj.book", null, msg.getBytes());
// 只有队列4能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "abc.def.food", null, msg.getBytes());
定义:广播类型的交换机与一个队列绑定时,不需要指定binding key。
路由规则:当消息发送到广播类型的交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。
例:
// 3个队列都会收到消息
channel.basicPublish("MY_FANOUT_EXCHANGE", "", null, msg.getBytes());
com.rabbitmq
amqp-client
5.5.2
/**
* @author King Chen
* @Date: 2019/3/24 12:46
*/
public class Producer {
private final static String QUEUE_NAME = "ORIGIN_QUEUE";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
//连接IP
factory.setHost("127.0.0.1");
//连接端口
factory.setPort(5672);
//虚拟机
factory.setHost("/");
//用户
factory.setUsername("guest");
factory.setPassword("guest");
//建立连接
Connection connection = factory.newConnection();
//创建消息通道
Channel channel = connection.createChannel();
String msg = "Hello world , Rabbit MQ";
//声明队列
//String queue, boolean durable , boolean exclusive , boolean autoDelete , Map arguments
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//发送消息(发送到默认交换机AMQP Default , Direct)
//如果有一个队列名称跟Routing Key相等,那么消息会路由到这个队列
//String exchange,String routingKey,BasicProperties props , byte[] body
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
channel.close();
connection.close();
}
}
/**
* @author King Chen
* @Date: 2019/3/24 15:21
*/
public class MyConsumer {
private final static String QUEUE_NAME = "ORIGIN_QUEUE";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
//连接IP
factory.setHost("127.0.0.1");
//默认监听端口
factory.setPort(5672);
//虚拟机
factory.setVirtualHost("/");
//设置访问用户
factory.setUsername("guest");
factory.setPassword("guest");
//建立连接
Connection connection = factory.newConnection();
//创建消息通道
Channel channel = connection.createChannel();
//声明队列
// String queue, boolean durable, boolean exclusive, boolean autoDelete,Map arguments
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println("Waiting for message...");
//创建消费者
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, "UTF-8");
System.out.println("Received message : '" + msg + "'");
}
};
//开始获取消息
//String queue, boolean autoAck , Consumer callback
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
声明交换机的参数
String type:交换机的类型,direct/topic/fanout中的一种。
boolean durable:是否持久化,代表交换机在服务器重启后是否还存在。
声明队列的参数
boolean durable:是否持久话,代表队列在服务器重启后是否还存在。
boolean exclusive:是否排他性队列,排他性队列只能在声明它的Connection中使用,连接断开时自动删除。
boolean autoDelete:是否自动删除。如果为true,至少有一个消息这连接到这个队列,之后所有与这个队列连接的消费者都断开时,队列会自动删除。
Map
消息属性BasicProperties
消息的全部属性有14个,一下列举了一些主要的参数:
参数 | 释义 |
Map |
消息的其他自定义参数 |
Integer deliveryMode | 持久化,其他:瞬态 |
Integer priority | 消息的优先级 |
String correlationId | 关联ID,方便RPC相应与请求关联 |
String replyTo | 回调队列 |
String expiration | TTL,消息过期时间,单位毫秒 |
有两种设置方式:
通过队列属性设置消息过期时间:
Map argss = new HashMap<>(16);
argss.put("x-message-ttl", 6000);
channel.queueDeclare(QUEUE_NAME, false, false, false, argss);
设置单条消息的过期时间:
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2)//持久化消息
.contentEncoding("UTF-8")
.expiration("10000")//TTL
.build();
channel.basicPublish("", QUEUE_NAME, properties, msg.getBytes());
注:链式构造,值得借鉴。
Map argss = new HashMap<>(16);
argss.put("x-message-ttl", 6000);
channel.queueDeclare(QUEUE_NAME, false, false, false, argss);
队列的过期时间决定了在没有任何消费者后,队列可以存活多久。
有三种情况消息会进入DLX(Dead Letter Exchange)私信交换机:
可以设置一个死信队列(Dead Letter Queue)与DLX绑定,即可以存储Dead Letter,消费者可以监听这个队列取走消息。
Map arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", "DLX_EXCHANGE");
// 指定了这个队列的死信交换机
channel.queueDeclare(QUEUE_NAME, false, false, false, arguments);
// 声明死信交换机
channel.exchangeDeclare("DLX_EXCHANGE", "topic", false, false, false, null);
// 声明死信队列
channel.queueDeclare("DLX_QUEUE", false, false, false, null);
// 绑定
channel.queueBind("DLX_QUEUE", "DLX_EXCHANGE", "#");
设置一个队列的最大优先级:
Map argss = new HashMap();
// 队列最大优先级
argss.put("x-max-priority",10);
channel.queueDeclare("ORIGIN_QUEUE", false, false, false, argss);
发送消息时指定消息当前的优先级:
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.priority(5) // 消息优先级
.build();
channel.basicPublish("", "ORIGIN_QUEUE", properties, msg.getBytes());
优先级高的消息可以优先被消费,但是,只有消息堆积(消息的发送速度大于消费者的消费速度)的情况下优先级才有意义。
RabbitMQ本身不支持延迟队列。可以使用TTL结合DLX的方式来实现消息的延迟投递,即把DLX跟某个队列绑定,到了指定时间,消息过期后,就会从DLX路由到这个队列,消费者可以从这个队列取走消息。
另一种方式是使用rabbitmq-delayed-message-exchange插件。
当然,将需要发送的信息保存在数据库,使用任务调度系统扫描然后发送也是可以实现的。
RabbitMQ实现RPC的原理:服务端处理消息后,把相应消息发送到一个相应队列,客户端再从相应队列取到结果。
其中的问题:Client收到消息后,怎么知道应答消息是回复哪一条消息的?所以必须有一个唯一ID来关联,就是correlationId。
RabbitMQ会在启动时检测机器的物理内存数值。默认当MQ占用40%以上内存时,MQ会主动抛出一个内存警告并阻塞所有连接(Connections)。可以通过修改rabbitmq.config文件来调整内存阈值,默认值时0.4,如下所示:[{rabbit,[{vm_memory_high_watermark,0.4}]}]。
默认情况,如果剩余磁盘空间在1GB以下,RabbitMQ主动阻塞所有的生产者。这个阈值也是可调的。
注意队列长度只在消息堆积的情况下有意义,而且会删除先入队的消息,不能实现服务端限流。
在AutoACK为false的情况下,如果一定数目的消息(通过基于consumer或者channel设置QoS的值)未被确认前,不进行消费新的消息。
//如果超过2条消息没有发送ACK,当前消费者不再接受队列消息
channel.basicQos(2);
channel.basicConsume(QUEUE_NAME, false, consumer);