《RabbitMQ实战指南》读书笔记——基础篇(Chapter 1~3)

1、什么是MQ

    MQ有点对点(P2P,Point-to-Point)和发布/订阅(Pub/Sub)两种模式。RabbitMQ属于P2P模式,但也可以通过将消息写入多个队列来实现发布订阅,如图所示。

《RabbitMQ实战指南》读书笔记——基础篇(Chapter 1~3)_第1张图片    MQ的作用概括如下:解耦(两边处理过程独立)、冗余(存储)、扩展性、削峰、可恢复性、顺序保证(一定程度上)、缓冲(控制数据流速度)、异步通信

《RabbitMQ实战指南》读书笔记——基础篇(Chapter 1~3)_第2张图片

    RabbitMQ是Erlang实现的AMQP(高级消息队列协议)的消息中间件,两者的模型架构一样。AMPQ协议可以看作结构化命令的集合,有Module Layer(定义命令)、Session Layer(为通信提供可靠性机制)、Transport Layer(物理层、链路层)三层。RabbitMQ有可靠性、路由灵活、扩展性、高可用性(镜像)、支持多种协议、多语言客户端、管理界面、插件机制等特点。运行时可以通过rabbitmq-server -detached以守护线程的方式在后台运行,默认端口号5672

 

2、概念

    从生产者到消费者,分为Producer,Exchange,Queue和Consumer,Exchange和Queue合称Broker,可以看作一个RabbitMQ服务节点,即一台服务器。Queue实际存储消息,而Exchange相对而言并不消耗性能。

    消息分为消息体(payload)和标签(label),标签表述这条消息,比如exchange名称或者routing key,消息路由时标签会丢弃,进入队列的只有消息体

    RabbitMQ不支持队列层面的广播消费,多个消费者订阅同一队列时会轮询,一条消息只能被一个Consumer消费,如果需要广播消费需要二次开发或者使用冗余的队列。

    Producer发送给Exchange时需要routing key,Exchange绑定Queue时需要binding key,两者是不一样的,需要联合使用才能生效,但有时会统称为路由键,同一组Exchange和Queue绑定时可以使用多个binding key。

    Exchange有fanout(把消息路由到所有与该Exchange绑定的Queue中)、direct(routing key=binding key)、topic(模糊匹配,如图)、headers(绑定Exchange和Queue时指定一组键值对,只有与消息体中的headers完全一致时才会路由到该队列,性能差,不实用)四种类型。

    RabbitMQ的API中通过channel(信道)来执行操作(创建Exchange和Queue等),channel是建立在Connection(TCP连接)之上的虚拟连接,复用了TCP连接,适用于channel流量不大时,流量很大时多开辟Connection。每个channel有唯一的ID,通常线程和channel一一对应

    如下列出了一些常用API。

// 设置最大未确认ACK数量
channel.basicQos(64);

// 拉模式
channel.basicGet();

// 请求Broker重新发送未被确认的消息
channel.basicRecover();

// 发送消息
channel.basicPublish();

// 确认
channel.basicAck();

// 声明交换器
channel.exchangeDeclare();

// 声明队列
channel.queueDeclare();

// 队列与交换器绑定
channel.queueBind();

 

3、API详解

(1)Connection,channel

ConnectionFactory factory = new Connection Factory();
factory.setUri("amqp://userName:password@ipAddress:portNumber/virtualHost");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel(); 

    也可以单独设置factory的这些参数。应用程序应该为每一个线程开辟一个channel,channel有isOpen()方法,if(channel.isOpen()){...},但要在if最外层加同步,不推荐使用,如果channel使用时处于关闭状态会抛出ShutdownSignalException

(2)exchangeDeclare

Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOEXception;

    exchangeDeclare有多个重载方法,exchange是交换器名称,type是交换器类型,durable表示是否持久化,autoDelete是否自动删除,自动删除的前提是至少有一个Queue或者Exchange与该Exchange绑定之后,都与该Exchange解绑,即无Queue/Exchange与该Exchange绑定时internal表示是否是内置交换器,内置交换器无法直接接收消息,只能通过Exchange路由到内置交换器。argument表示其他参数。

    此外还有void exchangeDeclareNoWait方法,不等待Broker返回(exchangeDeclare等待Broker返回Exchange.DeclareOk), 不推荐使用,因为客户端可能会在exchange没创建好时就使用它。

(3)queueDeclare

Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map argments) throws IOException;

    不带任何参数的queueDeclare默认创建排他的、自动删除的、非持久化的队列排他队列基于Connection可见,同一个Connection里的不同channel可共用,如果一个Connection声明了排他队列,其他Connection不允许声明同名队列。如果Connection关闭或者客户端退出,该排他队列会被自动删除(无论是否持久化),适用于一个客户端同时发送和读取消息的场景。

    autoDelete的前提是有消费者连接到这个队列,之后所有与这个队列连接的消费者都断开。

    如果订阅了一个队列,就无法声明队列了,必须取消订阅,将信道置为传输模式才能声明队列。即同一个channel不能同时生产和消费

    同样的,也有queueDeclareNoWait方法,还有Queue.PurgeOk queuePurge(String queue) throws IOException方法清空队列(不删除队列)。

(4)queueBind,绑定队列和交换器

Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map arguments) throws IOException;

(5)exchangeBind,交换器与交换器绑定

Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map arguments) throws IOException;

(6)basicPublish,发送消息

void basicPublish(String exchange, String routingKey, boolean mandatory, boolean imediate, BasicProperties props, byte[] body) throws IOException;

    props是消息的基本属性集,有contentType, contentEncoding, headers, deliveryMode, priority, correlationId, replyTo, expiration, messageId, timestamp, type, userId, appId, clusterId等14个属性。

    当mandatory为true时,Exchange找不到合适队列时会将消息返回给生产者(Basic.return),为false时丢弃消息,生产者通过channel.addReturnListener添加ReturnListener监听器来获取丢弃的消息。

    当immediate为true时,如果队列上没有消费者,则将消息返回给生产者,不用将消息存入队列等待消费者。即mandatory找不到队列时返回,immediate找不到消费者时返回。RabbitMQ 3.0之后去掉了对immediate的支持,官方的解释是会影响镜像队列的功能,增加复杂性,建议采用TTL和DLX(死信交换器)替代(设置很短的TTL,没有消费者则进入DLX)。

(7)消费分为推模式(Push,持续接收,Basic.Consume)和拉模式(Pull,接收单条,Basic.Get)

    推模式:

String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) throws IOException;

    Basic.Consume将channel置为投递模式。当autoAck为false,会等待消费者显示ACK(Basic.Ack)之后才从内存或磁盘中移除消息;为true时,发送完消息会自动删除,会导致消息丢失。如果RabbitMQ一直没有收到确认信号,并且消费者断开了连接,会将消息重新发送给下一个消费者。RabbitMQ不会为未确认的消息设置过期时间,重新投递的唯一依据是消费者连接是否已经断开(RabbitMQ允许消费者消费消息很久,所以如果消费者很久未收到消息,可以重新连接)。

    consumerTag用来区分多个消费者;noLocal为true表示不能将同一个Connection中生产者的消息发送给该Connection中的消费者;exclusive设置是否排他;Consumer的实现通常选用DefaultConsumer,重写handleDelivery方法。通常的做法是一个channel对应一个消费者,也可以对应多个,但是同一时刻只有一个消费者在消费。

    拉模式

GetResponse basicGet(String queue, boolean autoAck) throws IOException;

    单条地获取消息,不能循环get来代替Basic.Consume,性能很差,Basic.Get类似于TCP里的停等协议,而Basic.Consume类似于滑动窗口,可以通过Basic.Qos限制最大未ACK的消息数量。

(8)确认与拒绝

void basicReject(long deliveryTag, boolean requeue) throws IOException;

    basicReject只能拒绝单条消息,deliveryTag可以看做消息编号,requeue表示是否重新入队和发送。

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;

    multiple为false时和basicReject方法一样,为true表示拒绝deliveryTag编号之前所有未ACK的消息。

Basic.RecoverOk basicRecover(boolean requeue) throws IOException;

    重发消息,requeue为true可能会分配给不同的消费者(默认),为false发送给原消费者。

(9)关闭连接

    channel.close()不是必须的,connection.close()的时候channel会自动关闭。channel和connection有Open、Closing、Closed三种状态,转变为Closed时会调用ShutDownListener,可以获取到关闭原因。如果将ShutDownListener注册到Closed的对象上会立即执行。

你可能感兴趣的:(MQ)