rabbitMq 学习

109 阅读13分钟

# 概念

RoutingKey

用来指定这个消息的路由规则,而这个Routing Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定键(BindingKey)固定的情况下,生产者可以在发送消息给交换器时

Binding

绑定。RabbitMQ中通过绑定将交换器与队列关联起来

消息确认机制

为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement)。消费者在订阅队列时,可以指定autoAck参数,当autoAck等于false时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除)。当autoAck等于true时,RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息。

自动入列

当autoAck参数置为false,对于RabbitMQ服务端而言,队列中的消息分成了两个部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者确认信号的消息。如果RabbitMQ一直没有收到消费者的确认信号,并且消费此消息的消费者已经断开连接,则RabbitMQ会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。

发送方确认模式

将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一 ID)。如果 RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(notacknowledged,未确认)消息。

发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。

建立连接

ConnectionFactory factory = new ConnectionFactory();
factory.setHost("124.220.8.55");
factory.setUsername("admin");
factory.setPassword("123");
factoru.setPort("9871");
Connection connection = factory.newConnection();

生产者

exchangeDeclare方法

返回值是Exchange.DeclareOK,用来标识成功声明了一个交换器。

exchange:交换器的名称。

type:交换器的类型

durable:设置是否持久化。durable设置为true表示持久化,反之是非持久化。持久化可以将交换器存盘,在
服务器重启的时候不会丢失相关信息。

autoDelete:设置是否自动删除。autoDelete设置为true则表示自动删除。自动删除的前提是至少有一个队列
或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑。注意不能错误地把这
个参数理解为:“当与此交换器连接的客户端都断开时,RabbitMQ会自动删除本交换器”。

internal:设置是否是内置的。如果设置为true,则表示是内置的交换器,客户端程序无法直接发送消息到这
个交换器中,只能通过交换器路由到交换器这种方式。

argument:其他一些结构化参数

queueDeclare方法详解

(1) Queue.DeclareOk queueDeclare() throws IOException;

不带任何参数的queueDeclare方法默认创建一个由RabbitMQ命名的(类似这种amq.gen-
LhQz1gv3GhDOv8PIDabOXA名称,这种队列也称之为匿名队列)、排他的、自动删除的、非持久化的队列

(2) Queue.DeclareOk queueDeclare(String queue,boolean durable,boolean exclusive,boolean autoDelete,Map<String,Object>arguments) throws IOException;

queue:队列的名称。

durable:设置是否持久化。为true则设置队列为持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失相关信息。

exclusive:设置是否排他。为true则设置队列为排他的。如果一个队列被声明为排他队列,该队列仅对首次
声明它的连接可见,并在连接断开时自动删除。(注意点)排他队列是基于连接(Connection)可见的,同一
个连接的不同信道(Channel)是可以同时访问同一连接创建的排他队列;“首次”是指如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同;即使该队列是持久化的,一
旦连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列适用于一个客户端同时发送和读取消息的
应用场景。

autoDelete:设置是否自动删除。为true则设置队列为自动删除。自动删除的前提是:至少有一个消费者连接
到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。不能把这个参数错误地理解为:“当
连接到此队列的所有客户端断开时,这个队列自动删除”,因为生产者客户端创建这个队列,或者没有消费者客
户端与这个队列连接时,都不会自动删除这个队列。

arguments:设置队列的其他一些参数,如x-message-ttl、x-expires、x-max-length、x-max-length-bytes、x-dead-letter-exchange、x-dead-letter-routing-key、x-max-priority等。

queueBind方法(将队列和交换器绑定)

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

queue:队列名称;
exchange:交换器的名称;
routingKey:用来绑定队列和交换器的路由键;
argument:定义绑定的一些参数。

exchangeBind (以将交换器与队列绑定,也可以将交换器与交换器绑定)

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

basicPublish (发送消息)

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

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

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

exchange:交换器的名称,指明消息需要发送到哪个交换器中。如果设置为空字符串,则消息会被发送到
RabbitMQ默认的交换器中。
routingKey:路由键,交换器根据路由键将消息存储到相应的队列之中。
props:消息的基本属性集,其包含14个属性成员,分别有contentType、contentEncoding、headers
(Map&lt;StringObject&gt;)、deliveryMode、priority、correlationId、replyTo、expiration、messageId、timestamp、type、userId、appId、clusterId。其中常用的几种都在上面的示例
中进行了演示。
byte[] body:消息体(payload),真正需要发送的消息。

通过AMQP来配置消息属性

exchangeDeclarePassive

检测相应的交换器是否存在。如果存在则正常返回;如果不存在则抛出异常:404 channel exception,同时Channel也会被关闭。

queueDeclarePassive

检测相应的队列是否存在。如果存在则正常返回,如果不存在则抛出异常:404 channel exception,同时Channel也会被关闭。

交换器类型

fanout

把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。

direct

direct类型的交换器会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。

topic

BindingKey中可以存在两种特殊字符串“”和“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多规格单词(可以是零个)。

RoutingKey为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如“com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;

public static void topicExchange() throws Exception {
    String message = "top";
    Connection connection = Producer.creatConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare("topicExchange","topic",true,false,null);
    // 队列1绑定
    channel.queueDeclare("topicQueue1",true,false,false,null);
    channel.queueBind("topicQueue1","topicExchange","*.client.*");

    // 队列2绑定
    channel.queueDeclare("topicQueue2",true,false,false,null);
    channel.queueBind("topicQueue2","topicExchange","comTop.*");

    // 队列3绑定
    channel.queueDeclare("topicQueue3",true,false,false,null);
    channel.queueBind("topicQueue3","topicExchange","wxwButton.*");

    channel.basicPublish("topicExchange","test.client.key",false,null,message.getBytes());
}

image.png

消费者

RabbitMQ的消费模式分两种:推(Push)模式和拉(Pull)模式。推模式采用Basic.Consume进行消费,而拉模式则是调用Basic.Get进行消费。

image.png 接收消息一般通过实现Consumer接口或者继承DefaultConsumer类来实现。当调用与Consumer相关的API方法时,不同的订阅采用不同的消费者标签(consumerTag)来区分彼此,在同一个Channel中的消费者也需要通过唯一的消费者标签以作区分

Channel类中basicConsume方法

(1) String basicConsume(String queue,Consumer callback) throws IOException; (2) String basicConsume(String queue,boolean autoAck,Consumer callback) throws IOException; (3) String basicConsume(String queue,boolean autoAck,Map<String,Object>arguments,Consumer callback) throws IOException; (4) String basicConsume(String queue,boolean autoAck,String consumerTag,Consumer callback) throws IOException;

queue:队列的名称;
autoAck:设置是否自动确认。建议设成false,即不自动确认;
consumerTag:消费者标签,用来区分多个消费者;
noLocal:设置为true则表示不能将同一个Connection中生产者发送的消息传送给这个
Connection中的消费者;
exclusive:设置是否排他;
arguments:设置消费者的其他参数;
callback:设置消费者的回调函数。用来处理RabbitMQ推送过来的消息,比如
6DefaultConsumer,使用时需要客户端重写(override)其中的方法。

Channel.basicAck(用于肯定确认)

RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了

Channel.basicNack(用于批量否定确认)

void basicNack(long deliveryTag, boolean multiple, boolean requeue)

multiple参数设置为false则表示拒绝编号为deliveryTag的这一条消息,这时候basicNack和basicReject方法一样;multiple参数设置为true则表示拒绝deliveryTag编号之前所有未被当前消费者确认的消息。

Channel.basicReject(用于否定确认)

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

deliveryTag可以看作消息的编号,它是一个64位的长整型值,最大值是9223372036854775807。如果requeue参数设置为true,则RabbitMQ会重新将这条消息存入队列,以便可以发送给下一个订阅的消费者;如果requeue参数设置为false,则RabbitMQ立即会把消息从队列中移除,而不会把它发送给新的消费者。Basic.Reject命令一次只能拒绝一条消息

Basic.Recover

(1) Basic.RecoverOk basicRecover() throws IOException;
(2) Basic.RecoverOk basicRecover(boolean requeue) throws IOException;

这个channel.basicRecover方法用来请求RabbitMQ重新发送还未被确认的消息。如果requeue参数设置为true,则未被确认的消息会被重新加入到队列中,这样对于同一条消息来说,可能会被分配给与之前不同的消费者。如果requeue参数设置为false,那么同一条消息会被分配给与之前相同的消费者。默认情况下,如果不设置requeue这个参数,相当于channel.basicRecover(true),即requeue默认为true。

拉模式

通过channel.basicGet方法可以单条地获取消息,其返回值是GetRespone。Channel类的basicGet方法没有其他重载方法

队列类型

排他队列:如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。

临时队列:每当我们连接到 Rabbit 时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机
名称的队列,或者能让服务器为我们选择一个随机队列名称那就更好了。其次一旦我们断开了消费者的连
接,队列将被自动删除。
String queueName = channel.queueDeclare().getQueue();

注意点

生产者和消费者都能够使用queueDeclare来声明一个队列,但是如果消费者在同一个信道上订阅了另一个队列,就无法再声明队列了。必须先取消订阅,然后将信道置为“传输”模式,之后才能声明队列

多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,RabbitMQ不支持队列层面的广播消费

Basic.Consume将信道(Channel)置为接收模式,直到取消队列的订阅为止。在接收模式期间,RabbitMQ会不断地推送消息给消费者,当然推送消息的个数还是会受到Basic.Qos的限制。如果只想从队列获得单条消息而不是持续订阅,建议还是使用Basic.Get进行消费。但是不能将Basic.Get放在一个循环里来代替Basic.Consume,这样做会严重影响RabbitMQ的性能。如果要实现高吞吐量,消费者理应使用Basic.Consume方法。

Connection关闭的时候,Channel也会自动关闭。

进阶

mandatory和immediate是channel.basicPublish方法中的两个参数,它们都有当消息传递过程中不可达目的地时将消息返回给生产者的功能。

RabbitMQ提供的备份交换器(Alternate Exchange)可以将未能被交换器路由的消息(没有绑定队列或者没有匹配的绑定)存储起来,而不用返回给客户端。

mandatory

  • 当mandatory参数设为true时,交换器无法根据自身的类型和路由键找到一个符合条件的队列,那么RabbitMQ会调用Basic.Return命令将消息返回给生产者。
  • 当mandatory参数设置为false时,则消息直接被丢弃。

immediate

当immediate参数设为true时,如果交换器在将消息路由到队列时发现队列上并不存在任何消费者,那么这条消息将不会存入队列中。当与路由键匹配的所有队列都没有消费者时,该消息会通过Basic.Return返回至生产者。

备份交换器

对于备份交换器,总结了以下几种特殊情况: 如果设置的备份交换器不存在,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失。 如果备份交换器没有绑定任何队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失。 如果备份交换器没有任何匹配的队列,客户端和RabbitMQ服务端都不会有异常出现,此时消息会丢失。 如果备份交换器和mandatory参数一起使用,那么mandatory参数无效。

public static void backupExchange() throws Exception {
    Connection connection = Producer.creatConnection();
    Channel channel = connection.createChannel();
    Map<String, Object> args = new HashMap<>();
    args.put("alternate-exchange", "myAme");
    channel.exchangeDeclare("normalExchange", "direct", true, false, args);
    channel.exchangeDeclare("myAme", "fanout", false);
    channel.queueDeclare("msgExchange", false, false, false, null);
    channel.queueDeclare("alternateQueue", false, false, false, null);
    channel.queueBind("msgExchange", "normalExchange", "all");
    channel.queueBind("alternateQueue", "myAme", "");
    channel.basicPublish("normalExchange", "some", true, null, "消息发送".getBytes(StandardCharsets.UTF_8));
    channel.addReturnListener((int replyCode,
                               String replyText,
                               String exchange,
                               String routingKey,
                               AMQP.BasicProperties properties,
                               byte[] body) -> {
        System.out.println("信息已经回调");
    });
}

获取没有路由到队列的信息

public static void directExchange() throws IOException, TimeoutException {
    String message = "我是新来的信息";
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("124.220.8.55");
    connectionFactory.setPassword("123");
    connectionFactory.setUsername("admin");
    Connection connection = connectionFactory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare("userExchange", "direct", true, false, null);
    channel.queueDeclare("userQueue", true, false, false, null);
    channel.queueBind("userQueue", "userExchange", "userQueueRoutingKey");
    channel.basicPublish("userExchange", "userQueueRoutingKey1",true, null, message.getBytes(StandardCharsets.UTF_8));
    channel.addReturnListener((int replyCode,
                               String replyText,
                               String exchange,
                               String routingKey,
                               AMQP.BasicProperties properties,
                               byte[] body) -> {
        System.out.println(new String(body));
    });
}

image.png

过期时间(TTL)

TTL,Time to Live的简称,即过期时间。RabbitMQ可以对消息和队列设置TTL。

  • 如果两种方法一起使用,则消息的TTL以两者之间较小的那个数值为准。
  • 死信:消息在队列中的生存时间一旦超过设置的TTL值时

队列消息TTL

public static void ttlQueue() throws IOException, TimeoutException {
    String message = "我是超时时间";
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("124.220.8.55");
    connectionFactory.setPassword("123");
    connectionFactory.setUsername("admin");
    Connection connection = connectionFactory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare("ttlExchange", "direct", true, false, null);
    // 设置队列超时时间
    Map<String,Object> params = new HashMap<>();
    params.put("x-message-ttl",6000);
    channel.queueDeclare("ttlQueue", true, false, false, params);
    channel.queueBind("ttlQueue", "ttlExchange", "ttlQueueRoutingKey");
    channel.basicPublish("ttlExchange", "ttlQueueRoutingKey",true, null, message.getBytes(StandardCharsets.UTF_8));

}

如果将TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃 通过队列属性设置,队列中所有消息都有相同的过期时间。 一旦消息过期,立即删除消息

消息的TTL

  • 对消息本身进行单独设置,每条消息的TTL可以不同。
  • 针对每条消息设置TTL的方法是在channel.basicPublish方法中加入expiration的属性参数,单位为毫秒。
  • 消息会在即将投递给消费者的时候绑定是否过期
public static void messageTtlQueue() throws IOException, TimeoutException {
    String message = "messageTtlQueue";
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("124.220.8.55");
    connectionFactory.setPassword("123");
    connectionFactory.setUsername("admin");
    Connection connection = connectionFactory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare("ttlExchange", "direct", true, false, null);
    channel.queueDeclare("messageTtlQueue", true, false, false, null);
    channel.queueBind("messageTtlQueue", "ttlExchange", "ttlQueueRoutingKey");
    AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder().expiration("6000");
    AMQP.BasicProperties  basicProperties = builder.build();
    channel.basicPublish("ttlExchange", "ttlQueueRoutingKey",true, basicProperties , message.getBytes(StandardCharsets.UTF_8));

}

设置队列的TTL

  • 通过channel.queueDeclare方法中的x-expires参数可以控制队列被自动删除前处于未使用状态的时间。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并且在过期时间段内也未调用过Basic.Get命令。
  • 不能设置为0

死信队列

DLX,全称为Dead-Letter-Exchange,当消息在一个队列中变成死信(dead message)之后,它能被重新被发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就称之为死信队列。

消息变成死信的一般情况

  • 消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false;
  • 消息过期;
  • 队列达到最大长度。

设置死信交换器

  • 通过在channel.queueDeclare方法中设置x-dead-letter-exchange参数
  • 为这个DLX指定路由键,如果没有特殊指定,则使用原队列的路由键
public static void deadQueue() throws Exception {
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("124.220.8.55");
    connectionFactory.setPassword("123");
    connectionFactory.setUsername("admin");
    Connection connection = connectionFactory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare("haoDeadExchange", "direct", true, false, null);
    channel.exchangeDeclare("deadExchange", "direct", true, false, null);
    // 设置对应的死信交换器和routingKey
    Map<String, Object> args = new HashMap<>();
    args.put("x-message-ttl", 10000);
    args.put("x-dead-letter-exchange", "deadExchange");
    args.put("x-dead-letter-routing-key", "dlxRouteKey");
    channel.queueDeclare("haoDeadQueue", true, false, false, args);
    channel.queueBind("haoDeadQueue", "haoDeadExchange", "ttlQueueRoutingKey1", null);

    channel.queueDeclare("deadQueue", false, false, false, null);
    channel.queueBind("deadQueue", "deadExchange", "dlxRouteKey");

    channel.basicPublish("haoDeadExchange", "ttlQueueRoutingKey1", MessageProperties.PERSISTENT_TEXT_PLAIN, "dbk".getBytes());
}

延迟队列

在AMQP协议中,或者RabbitMQ本身没有直接支持延迟队列的功能,可以通过前面所介绍的DLX和TTL模拟出延迟队列的功能。

image.png

优先级队列

  • 优先级高的消息具备优先被消费的特权。
  • 默认最低为0,最高为队列设置的最大优先级。
  • 设置队列的x-max-priority参数来实现

消息的优先级

AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder().priority(100);
AMQP.BasicProperties basicProperties = builder.build();

持久化

RabbitMQ的持久化分为三个部分:交换器的持久化、队列的持久化和消息的持久化。

消息的持久化

通过将消息的投递模式(BasicProperties中的deliveryMode属性)设置为2即可实现消息的持久化。(MessageProperties.PERSISTENT_TEXT_PLAIN)封装了以上的属性·

生产者确认

通过事务机制实现;

channel.txSelect

用于将当前的信道设置成事务模式

channel.txCommit

用于提交事务

channel.txRollback。

用于事务回滚。

public static void transaction() throws Exception {
    Connection connection = Producer.creatConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare("transactionExchange", "direct", true, false, null);
    channel.queueDeclare("transactionQueue", true, false, false, null);
    channel.queueBind("transactionQueue", "transactionExchange", "transactionExchange");
    try {
        channel.txSelect();
        channel.basicPublish("transactionExchange", "transactionExchange", null, "事务的数据".getBytes());
        channel.txCommit();
    } catch (IOException e) {
        e.printStackTrace();
        channel.txRollback();
    }
}

image.png

发送方确认机制

客户端值的意思

D durable
TTL 过期时间
DLX Dead-Letter-Exchange
DLK x-dead-letter-routing-key
Pri 优先级

springboot 整合

简单整合

引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

更改配置

spring
   rabbitmq:
     host: 124.220.8.55
     port: 5672
     username: admin
     password: 123
     listener:
       simple:
         acknowledge-mode: manual

交换器和队列配置

@Configuration
public class BaseExchangeAndQueueConfig {

    // 创建队列
    @Bean
    Queue baseQueue() {
        return new Queue("baseQueue");
    }

    //创建交换器
    @Bean
    DirectExchange baseExchange() {
        return new DirectExchange("baseExchange",true,false);
    }

    // 交换器绑定
    @Bean
    Binding baseBinding() {
        return BindingBuilder.bind(baseQueue()).to( baseExchange()).with("baseRouting");
    }

}

新增工具类,发送消息

@Component
public class BaseRabbitMqUtil {

    @Resource
    RabbitTemplate rabbitTemplate;

    public String sendMsg (String exchange, String routingKey, Object message) {
        rabbitTemplate.convertAndSend(exchange,routingKey,message,new CorrelationData());
        return "";
    }

}
@GetMapping("/consumeMsg")
public String consumeMsg(@RequestBody Map<String, Object> params) {
    return baseRabbitMqUtil.sendMsg("baseExchange","baseRouting","发送消息");
}

消费消息

@Component
@RabbitListener(queues = {"baseQueue"})
public class BaseConsumeHandler {

    @RabbitHandler
    public void process(String msg) {
        System.out.println(msg);
    }


}

继续学习

www.jianshu.com/c/3ab8f98cf…