rabbitmq总结

rabbitmq简介

rabbitmq 是spring所在公司Pivotal自己的产品 是基于AMQP(advanced message queue protocol)高级队列协议的消息中间件 采用erlang开发 因此安装需要erlang环境 具体安装根据自己的环境 因为跟spring有共同的血缘关系 所以spring 全家桶对其的支持应该是相当完善的
简单概念
一般消息队列 都是生产者将消息发送到队列 消费者监听队列进行消费 rabbitmq 一个虚拟主机(默认 /)持有一个或者多个交换机(Exchange) 用户只能在虚拟主机的粒度进行权限控制 交换机根据一定的策略(RoutingKey)绑定(Binding)到队列(Queue)上 这样生产者和队列就没有直接联系 而是将消息发送的交换机 交换机再把消息转发到对应绑定的队列上

  • 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上定义了大量的编程接口。

三种主要的交换机

Direct Exchange 直连交换机
定义:直连类型的交换机与一个队列绑定时,需要指定一个明确的binding key。 路由规则:发送消息到直连类型的交换机时,只有routing key跟binding key完全匹配时,绑定的队列才能收到消 息。
例如:

//  只有队列1能收到消息 
channel.basicPublish("MY_DIRECT_EXCHANGE", "key1", null, msg.getBytes()); 

rabbitmq总结_第1张图片
Topic Exchange 主题交换机
定义:主题类型的交换机与一个队列绑定时,可以指定按模式匹配的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());  

rabbitmq总结_第2张图片

Fanout Exchange 广播交换机
定义:广播类型的交换机与一个队列绑定时,不需要指定binding key。 路由规则:当消息发送到广播类型的交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。

// 3个队列都会收到消息 
channel.basicPublish("MY_FANOUT_EXCHANGE", "", null, msg.getBytes());

rabbitmq总结_第3张图片

RabbitMQ之mandatory和immediate

在生产者通过channel的basicPublish方法发布消息时,通常有几个参数需要设置,为此我们有必要了解清楚这些参数代表的具体含义及其作用,查看Channel接口,会发现存在3个重载的basicPublish方法

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

他们共有的参数分别是:
exchange:交换机名称
routingKey:路由键
props:消息属性字段,比如消息头部信息等等
body:消息主体部分
除此之外,还有mandatory和immediate这两个参数,鉴于RabbitMQ3.0不再支持immediate标志,因此我们重点讨论mandatory标志
mandatory的作用:
当mandatory标志位设置为true时,如果exchange根据自身类型和消息routingKey无法找到一个合适的queue存储消息,那么broker会调用basic.return方法将消息返还给生产者;当mandatory设置为false时,出现上述情况broker会直接将消息丢弃;通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者;
immediate的作用:****(新版本未启用,其作用可以使用 TTL+DLX 代替)
当immediate标志位设置为true时,如果exchange在将消息route到queue(s)时发现对应的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。

概括来说,mandatory标志告诉服务器至少将该消息route到一个队列中,否则将消息返还给生产者;immediate标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。

我们该怎么获取到没有被正确路由到合适队列的消息呢?这时候可以通过为channel信道设置ReturnListener监听器来实现,具体代码(main函数部分):

try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost(ip);
            factory.setPort(port);
            factory.setUsername(username);
            factory.setPassword(password);

            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();

            channel.basicQos(1);
            channel.basicPublish(exchangeName, "", mandatory, immediate, MessageProperties.PERSISTENT_TEXT_PLAIN, "===mandatory===".getBytes());
            channel.addReturnListener(new ReturnListener() {
                public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] body) throws IOException {
                    String message = new String(body);
                    System.out.println("Basic.return返回的结果是:"+message);
                }
            });
//            channel.close();
//            connection.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

RabbitMQ之可靠投递

参考地址:https://www.cnblogs.com/wangiqngpei557/p/9381478.html

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两个选项用来控制消息的投递可靠性模式。
rabbitmq 整个消息投递的路径为:
producer->rabbitmq broker cluster->exchange->queue->consumer
rabbitmq总结_第4张图片

message 从 producer 到 rabbitmq broker cluster 则会返回一个 confirmCallback 。
message 从 exchange->queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的最终一致性和部分纠错能力。

一 ,confirmCallback 确认模式

在创建 connectionFactory 的时候设置 PublisherConfirms(true) 选项,开启 confirmcallback 。

CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setPublisherConfirms(true);//开启confirm模式


RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
rabbitTemplate.setConfirmCallback((data, ack, cause) -> {
        if (!ack) {
               log.error("消息发送失败!" + cause + data.toString());
        } else {
            log.info("消息发送成功,消息ID:" + (data != null ? data.getId() : null));
        }
    });

我们来看下 ConfirmCallback 接口

public interface ConfirmCallback {

        /**
         * Confirmation callback.
         * @param correlationData correlation data for the callback.
         * @param ack true for ack, false for nack
         * @param cause An optional cause, for nack, when available, otherwise null.
         */
        void confirm(CorrelationData correlationData, boolean ack, String cause);

    }

重点是 CorrelationData 对象,每个发送的消息都需要配备一个 CorrelationData 相关数据对象,CorrelationData 对象内部只有一个 id 属性,用来表示当前消息唯一性。

发送的时候创建一个 CorrelationData 对象。

User user = new User();
user.setID(1010101L);
user.setUserName("plen");

rabbitTemplate.convertAndSend(exchange, 
        routing, 
        user,
        message -> {
        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
            return message;
        },
       new CorrelationData(user.getID().toString()));

这里将 user ID 设置为当前消息 CorrelationData id 。当然这里是纯粹 demo,真实场景是需要做业务无关消息 ID 生成,同时要记录下这个 id用来纠错和对账。

消息只要被 rabbitmq broker 接收到就会执行 confirmCallback,如果是 cluster 模式,需要所有 broker 接收到才会调用 confirmCallback。
被 broker 接收到只能表示 message 已经到达服务器,并不能保证消息一定会被投递到目标 queue 里。所以需要用到接下来的 returnCallback。

二,returnCallBack 未投递到queue退回模式

confrim 模式只能保证消息到达 broker,不能保证消息准确投递到目标 queue 里。在有些业务场景下,我们需要保证消息一定要投递到目标 queue 里,此时就需要用到 return 退回模式。
同样创建 ConnectionFactory 到时候需要设置 PublisherReturns(true) 选项。

CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setPublisherReturns(true);//开启return模式

rabbitTemplate.setMandatory(true);//开启强制委托模式

rabbitTemplate.setReturnCallback((message, replyCode, replyText,
                    exchange, routingKey) ->
    log.info(MessageFormat.format("消息发送ReturnCallback:{0},{1},{2},{3},{4},{5}", message, replyCode, replyText, exchange, routingKey)));

这样如果未能投递到目标 queue 里将调用 returnCallback ,可以记录下详细到投递数据,定期的巡检或者自动纠错都需要这些数据。

RabbitMQ之TTL(Time-To-Live 过期时间)

1. 概述
RabbitMQ可以对消息和队列设置TTL. 目前有两种方法可以设置。
第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。
第二种方法是对消息进行单独设置,每条消息TTL可以不同。
如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message, 消费者将无法再收到该消息。

2,设置队列属性
通过队列属性设置消息TTL的方法是在queue.declare方法中加入x-message-ttl参数,单位为ms.
例如:

e com.vms.test.zzh.rabbitmq.self;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.apache.commons.collections.map.HashedMap;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

/**
 * Created by hidden on 2017/2/7.
 */
public class RBttlTest {
    public static final String ip = "xx.xx.xx.73";
    public static final int port = 5672;
    public static final String username = "root";
    public static final String password = "root";

    public static final String queueName = "queue.ttl.test";
    public static final String exchangeName = "exchange.ttl.test";
    public static final String routingKey = "ttl";
    public static final Boolean durable = true;
    public static final Boolean exclusive = false;
    public static final Boolean autoDelete = false;

    public static void main(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost(ip);
            factory.setPort(port);
            factory.setUsername(username);
            factory.setPassword(password);

            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();

            Map  argss = new HashMap();
            argss.put("vhost", "/");
            argss.put("username","root");
            argss.put("password", "root");
            argss.put("x-message-ttl",6000);
            channel.queueDeclare(queueName, durable, exclusive, autoDelete, argss);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
}

通过RabbitMQ的管理页面可以看到有新的queue生成,并标记为TTL(上面的代码同时会将此queue设置为durable=true,以及包含相关参数,比如vhost=/),如下图所示:

另外也可以同rabbitmq的命令行模式来设置:

rabbitmqctl set_policy TTL ".*" '{"message-ttl":60000}' --apply-to queues

还可以通过HTTP接口调用:

$ curl -i -u guest:guest -H "content-type:application/json"  -XPUT -d'{"auto_delete":false,"durable":true,"arguments":{"x-message-ttl": 60000}}' 
http://localhost:15672/api/queues/{vhost}/{queuename}

如果不设置TTL,则表示此消息不会过期。如果将TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃,这个特性可以部分替代RabbitMQ3.0以前支持的immediate参数,之所以所部分代替,是应为immediate参数在投递失败会有basic.return方法将消息体返回(这个功能可以利用死信队列来实现)。

3. 设置消息属性
针对每条消息设置TTL的方法是在basic.publish方法中加入expiration的属性参数,单位为ms.
关键代码:

AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.deliveryMode(2);
builder.expiration(“6000”);
AMQP.BasicProperties properties = builder.build();

channel.basicPublish(exchangeName,routingKey,mandatory,properties,"ttlTestMessage".getBytes());

也可以写成:

AMQP.BasicProperties properties = new AMQP.BasicProperties();
properties.setExpiration("60000");
channel.basicPublish(exchangeName,routingKey,mandatory,properties,"ttlTestMessage".getBytes());

具体代码如下所示:

public static void main(String[] args) throws InterruptedException {
    sendTTLMessage();
    TimeUnit.SECONDS.sleep(5);
    consumeTTLMessage();
}

public static void sendTTLMessage(){
    try {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(ip);
        factory.setPort(port);
        factory.setUsername(username);
        factory.setPassword(password);

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
        builder.deliveryMode(2);
        builder.expiration("6000");
        AMQP.BasicProperties  properties = builder.build();

        channel.basicPublish(exchangeName,routingKey,mandatory,properties,"ttlTestMessage".getBytes());

        channel.close();
        connection.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        e.printStackTrace();
    }
}

public static void consumeTTLMessage(){
    try {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(ip);
        factory.setPort(port);
        factory.setUsername(username);
        factory.setPassword(password);

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true, consumer);
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        String message = new String(delivery.getBody());
        System.out.println(" [X] Received '" + message + "'");

        channel.close();
        connection.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

当TimeUnit.SECONDS.sleep(5);设置为5s时可以消费到消息,当设置为7s时,则消费不到消息,因为此时已经超时了。
还可以通过HTTP接口调用如下:

$ curl -i -u guest:guest -H "content-type:application/json"  -XPOST -d'{"properties":{"expiration":"60000"},"routing_key":"routingkey","payload":"my body","payload_encoding":"string"}'  http://localhost:15672/api/exchanges/{vhost}/{exchangename}/publish

4,对比

第一种设置队列TTL属性的方法,一旦消息过期,就会从队列中抹去。
第二种方法里,即使消息过期,也不会马上从队列中抹去,因为每条消息是否过期时在即将投递到消费者之前判定的。
为什么两者得处理方法不一致?因为第一种方法里,队列中已过期的消息肯定在队列头部,RabbitMQ只要定期从队头开始扫描是否有过期消息即可,而第二种方法里,每条消息的过期时间不同,如果要删除所有过期消息,势必要扫描整个队列,所以不如等到此消息即将被消费时再判定是否过期,如果过期,再进行删除。

5,Queue TTL

queue.declare 命令中的 x-expires 参数控制 queue 被自动删除前可以处于未使用状态的时间。未使用的意思是 queue 上没有任何 consumer ,queue 没有被重新声明,并且在过期时间段内未调用过 basic.get 命令。该方式可用于,例如,RPC-style 的回复 queue, 其中许多 queue 会被创建出来,但是却从未被使用。

服务器会确保在过期时间到达后 queue 被删除,但是不保证删除的动作有多么的及时。在服务器重启后,持久化的 queue 的超时时间将重新计算。

用于表示超期时间的 x-expires 参数值以毫秒为单位,并且服从和 x-message-ttl 一样的约束条件,且不能设置为 0 。所以,如果该参数设置为 1000 ,则表示该 queue 如果在 1s之内未被使用则会被删除。

下面的 Java 示例创建了一个 queue ,其会在 30 分钟不使用的情况下判定为超时。

Map args = new HashMap();  
args.put("x-expires", 1800000);  
channel.queueDeclare("myqueue", false, false, false, args);  

rabbitmq 之死信队列(延迟消费消息)

参考文章:https://my.oschina.net/10000000000/blog/1626278
https://www.cnblogs.com/toov5/p/10288260.html

代码DEMO: https://github.com/tengxvincent/spring-boot-vincent.git

概念
死信队列 听上去像 消息“死”了 其实也有点这个意思,死信队列 是 当消息在一个队列 因为下列原因:

  • 消息被拒绝 basic.reject 单条消息拒绝/ basic.nack 批量拒绝 并且不再重新投递 requeue=false
  • 消息超期 (rabbitmq Time-To-Live -> messageProperties.setExpiration())
  • 队列超载

变成了“死信”后,被重新投递(publish)到另一个Exchange,该Exchange 就是DLX ,然后该Exchange 根据绑定规则,转发到对应的 队列上,监听该队列就可以重新消费,说白了就是没有被消费的消息换个地方重新被消费

生产者 --> 消息 --> 交换机 --> 队列 --> 变成死信 --> DLX交换机 -->队列 --> 消费者

如果高并发情况到来 某一个队列比如邮件队列满了 或者异常 或者消息过期 或者消费者拒绝消息
rabbitmq总结_第5张图片

springboot rabbitmq 死信队列实践
下面我们模拟一个死信队列的应用场景 消息延时处理
项目中 RabbitConfig 死信相关片段:

 /**
     * 死信队列跟交换机类型没有关系 不一定为directExchange  不影响该类型交换机的特性.
     *
     * @return the exchange
     */
    @Bean("deadLetterExchange")
    public Exchange deadLetterExchange() {
        return ExchangeBuilder.directExchange("DL_EXCHANGE").durable(true).build();
    }

    /**
     * 声明一个死信队列.
     * x-dead-letter-exchange   对应  死信交换机
     * x-dead-letter-routing-key  对应 死信队列
     *
     * @return the queue
     */
    @Bean("deadLetterQueue")
    public Queue deadLetterQueue() {
        Map args = new HashMap<>(2);
//       x-dead-letter-exchange    声明  死信交换机
        args.put(DEAD_LETTER_QUEUE_KEY, "DL_EXCHANGE");
//       x-dead-letter-routing-key    声明 死信路由键
        args.put(DEAD_LETTER_ROUTING_KEY, "KEY_R");
        return QueueBuilder.durable("DL_QUEUE").withArguments(args).build();
    }

    /**
     * 定义死信队列转发队列.
     *
     * @return the queue
     */
    @Bean("redirectQueue")
    public Queue redirectQueue() {
        return QueueBuilder.durable("REDIRECT_QUEUE").build();
    }

    /**
     * 死信路由通过 DL_KEY 绑定键绑定到死信队列上.
     *
     * @return the binding
     */
    @Bean
    public Binding deadLetterBinding() {
        return new Binding("DL_QUEUE", Binding.DestinationType.QUEUE, "DL_EXCHANGE", "DL_KEY", null);

    }

    /**
     * 死信路由通过 KEY_R 绑定键绑定到死信队列上.
     *
     * @return the binding
     */
    @Bean
    public Binding redirectBinding() {
        return new Binding("REDIRECT_QUEUE", Binding.DestinationType.QUEUE, "DL_EXCHANGE", "KEY_R", null);
    }

说明:
deadLetterExchange()声明了一个Direct 类型的Exchange (死信队列跟交换机没有关系)
deadLetterQueue() 声明了一个队列 这个队列 跟前面我们声明的队列不一样 注入了 Map 参数 下面的概念非常重要
x-dead-letter-exchange 来标识一个交换机 x-dead-letter-routing-key 来标识一个绑定键(RoutingKey) 这个绑定键 是分配给 标识的交换机的 如果没有特殊指定 声明队列的原routingkey , 如果有队列通过此绑定键 绑定到交换机 那么死信会被该交换机转发到 该队列上 通过监听 可对消息进行消费
可以打个比方 这个是为主力队员 设置了一个替补 如果主力 “死”了 他的活 替补接手 这样更好理解
deadLetterBinding() 对这个带参队列 进行了 和交换机的规则绑定 等下 消费者 先把消息通过交换机投递到该队列中去 然后制造条件发生“死信”
redirectBinding() 我们需要给标识的交换机 以及对其指定的routingkey 来绑定一个所谓的“替补”队列 用来监听

流程具体是 消息投递到 DL_QUEUE 10秒后消息过期 生成死信 然后转发到 REDIRECT_QUEUE 通过对其的监听 来消费消息
SendController 增加消费发送接口

 /**
     * 测试死信队列.
     *
     * @param p the p
     * @return the response entity
     */
    @RequestMapping("/dead")
    public ResponseEntity deadLetter(String p) {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
//        声明消息处理器  这个对消息进行处理  可以设置一些参数   对消息进行一些定制化处理   我们这里  来设置消息的编码  以及消息的过期时间  因为在.net 以及其他版本过期时间不一致   这里的时间毫秒值 为字符串
        MessagePostProcessor messagePostProcessor = message -> {
            MessageProperties messageProperties = message.getMessageProperties();
//            设置编码
            messageProperties.setContentEncoding("utf-8");
//            设置过期时间10*1000毫秒
            messageProperties.setExpiration("10000");
            return message;
        };
//         向DL_QUEUE 发送消息  10*1000毫秒后过期 形成死信
        rabbitTemplate.convertAndSend("DL_EXCHANGE", "DL_KEY", p, messagePostProcessor, correlationData);
        return ResponseEntity.ok();
    }

监听 REDIRECT_QUEUE

/**
     * 监听替补队列 来验证死信.
     *
     * @param message the message
     * @param channel the channel
     * @throws IOException the io exception  这里异常需要处理
     */
    @RabbitListener(queues = {"REDIRECT_QUEUE"})
    public void redirect(Message message, Channel channel) throws IOException {
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        log.debug("dead message  10s 后 消费消息 {}",new String (message.getBody()));
    }

测试死信队列接口
rabbitmq总结_第6张图片

实践经验总结
1,配置文件与命名规范
2,调用封装
3,消息落库(可重塑,可重发)+定时任务—》效率降低占用磁盘空间
4,减少连接数 —》可以将消息打包成集合发送(JSON),注意一次发送数据超过4M
5, 生产者先发送消息还是先登记业务表?—》先登记业务表,再发送消息
6,谁来创建对象(交换机,队列,绑定关系)? —》谁使用,谁创建… 所以是消费者创建

rabbitmq总结_第7张图片

1 MQ 使用场景?
异步:批量数据异步处理 例:批量上传文件,日志埋点,订单全流程状态监控
削峰:高负载任务负载均衡 例:电商秒杀 统一系统中 自己发送给自己
解耦:串行任务并行化 例 抓单系统–》订单系统
广播:基于发布/订阅模式实现一对多通信。 例: 多用户发送邮件

3 多个消费者监听一个生产者时,消息如何分发?
默认使用 Round-Robin (轮询)
Round-Robin (轮询)
–》处理能力弱的消费者 可能消息堆积
–》使用在消费者端 非自动ACK的前提下 如果一定数目的消息(通过基于consume或者channel设置Qos的值)未被确认前,不进行消费新的消息
—》这样可以设置消费者端限流
—》实现 Fair dispatch 分发

4 无法被路由的消息,去了哪里?

可能因为路由关键字错误,或者队列不存在,或者队列名称错误导致②失败。
1,使用mandatory参数和ReturnListener,可以实现消息无法路由的时候返回给生产者。
2,另一种方式就是使用备份交换机(alternate-exchange),无法路由的消息会发送到这个交换机上。
Map arguments = new HashMap(); arguments.put(“alternate-exchange”,“ALTERNATE_EXCHANGE”); // 指定交换机的备份交换机

channel.exchangeDeclare(“TEST_EXCHANGE”,“topic”, false, false, false, arguments);

6 如何实现延时队列?

RabbitMQ本身不支持延迟队列。可以使用TTL结合DLX的方式来实现消息的延迟投递,即把DLX跟某个队列绑定, 到了指定时间,消息过期后,就会从DLX路由到这个队列,消费者可以从这个队列取走消息。 另一种方式是使用rabbitmq-delayed-message-exchange插件。
当然,将需要发送的信息保存在数据库,使用任务调度系统扫描然后发送也是可以实现的。

8 如何在服务端和消费端做限流
服务端流控(Flow Control)
–》内存40% 以上 磁盘 余磁盘空间在 1GB 以下
RabbitMQ 会在启动时检测机器的物理内存数值。默认当 MQ 占用 40% 以上内存时,MQ 会主动抛出一个内存警 告并阻塞所有连接(Connections)。可以通过修改 rabbitmq.config 文件来调整内存阈值,默认值是 0.4,如下 所示: [{rabbit, [{vm_memory_high_watermark, 0.4}]}]. 默认情况,如果剩余磁盘空间在 1GB 以下,RabbitMQ 主动阻塞所有的生产者。这个阈值也是可调的。
注意队列长度只在消息堆积的情况下有意义,而且会删除先入队的消息,不能实现服务端限流。
消费端限流
在AutoACK为false的情况下,如果一定数目的消息(通过基于consumer或者channel设置Qos的值)未被确认 前,不进行消费新的消息。

9 消息的顺序性
消息的顺序性指的是消费者消费的顺序跟生产者产生消息的顺序是一致的。
在RabbitMQ中,一个队列有多个消费者时,由于不同的消费者消费消息的速度是不一样的,顺序无法保证。 参考:消息:1、新增门店 2、绑定产品 3、激活门店,这种情况下消息消费顺序不能颠倒。

可以使用全局ID的方式
–》为每一个消息设置一个ID和parent ID
–》在消费端 保证这条消息的parent ID 的消息没有处理完 就不处理当前这条消息

10 如何保证消息的幂等性?
每一个消息都关联一个当前系统的唯一业务ID

你可能感兴趣的:(RabbitMQ)