对RabbitMQ的理解

先讲讲MQ

MQ 是(Message Queue)的缩写,意思也很好理解就是消息队列,所以MQ其实通俗的说就是存放消息的一个容器,队列就是一种存储方式先进先出,MQ通常用于分布式系统之间的通信.

MQ的优势

现在分布式的流行,才出现这个MQ,

  1. 应用解耦 ,通常情况是 :
    有一个订单系统 用户访问订单系统,订单系统会发消息告诉商品系统,用户系统,物流系统有订单,情况是订单系统要等他们每个系统都添加数据完成后订单系统才会执行下去并反馈给用户,但是如果其中一个系统宕机了,那订单系统就会一直等,用户体验极差,这是一种情况
    第二种情况是增加了新的系统,这时候就要修改订单系统,让订单系统在发送给A系统,业务的扩张,可能后面的需要增加的更多,这时候就需要频繁的修改订单系统,维护性差
    解决方案:
    增加MQ(也就是中间件)后,订单系统只需要把消息发给中间件,直接给用户响应,后面库存系统等处理消息跟订单系统没有关系,库存系统如果挂了,在库存系统启动后再去MQ调用继续处理,将这两方真正解耦了,如果要增加其他系统,那这个系统只需要去MQ拿取消息就行,订单系统不需要做修改
    系统的耦合性越高,容错性就越低,可维护性就越低性提高
    2.异步提速
    提供者不需要在等待消费者处理数据,而是将数据发送给中间件就可以返回,节省很多中间时间
    3 削峰填谷
    像高并发情况,大型促销,突然增加的访问量超出了订单系统的承受范围,订单系统可能就崩溃了,增减MQ,用MQ来储存用户高并发的请求,而订单系统保证每秒固定的处理速度,保证了订单系统的稳定,这叫削峰,当促销时间过后,访问量没有那么大,订单系统也可以保证拿取MQ中的所有消息继续处理,而不是一下子就空下来这叫填谷

MQ的劣势

1.系统可用性降低
因为MQ的加入,其实系统引入的依赖越多,稳定性就越差,消息全靠MQ,如果MQ宕机了,整个系统就停了,影响比较大
2.系统的复杂性提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
3.一致性问题
a系统处理完业务,通过中间件给BCD三个系统发消息数据,如果B系统处理处理成功,但是D系统处理失败,如何保证消息的一致性

MQ的适用场景

1.消息生产者不需要得到接收方的反馈
2.容许短暂的不一致可能用户下完订单,订单系统反馈下订单成功,但是物流系统还没执行到这个数据,但是不代表不执行,也不代表失败,所以要允许短暂的不一致
3.利大于弊 优势很大,但是劣势也很复杂,如果只是一个简单的系统就没必要使用MQ

目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及 MQ 产品特征,综合考虑。

RabbitMQ

RabbitMQ中有个关键词AMQP;
AMQP: 是一个消息协议,类似于HTTP;
JMS:是Sun公司定义的关于消息发送的接口;

2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。

RabbitMQ 基础架构如下图:
RabbitMQ 基础架构
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker 整个中间件整体
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
*Connection:*publisher/consumer 和 broker 之间的 TCP 连接
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
Queue:消息最终被送到这里等待 consumer 取走
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

**RabbitMQ 提供了 6 种工作模式:

简单模式: 一个生产者,一个队列,生产者通过管道连接交换机连接到队列将消息发送到队列中,消费者通过管道连接队列取到数据

work queues 工作模式:一个交换机,一个队列,两个消费者,生产者通过管道连接交换机发送消息到队列,两个消费者同时监听这个队列,那这两个消费者是竞争关系,轮流拿取数据,当数据量大的时候也就会起到负载均衡的作用;

Publish/Subscribe 发布与订阅模式:一个生产者,一个交换机,两个队列,两个消费者,一个消费者绑定一个队列,当生产者将消息发给交换机,交换机会给他所有绑定的队列都发送消息,这两个队列都会拥有相同的消息,那两个消费者也会取到同样的数据;

Routing 路由模式 : 一个生产者,一个交换机,两个队列,两个消费者,这里可以设置路由key,路由key的作用是可以决定将消息发送个那个队列,在设置交换机和队列绑定的时候,会设置路由key,发送消息的时候需要制定交换机和路由key,交换机去取找符合路由key的队列,和这个队列相对的消费者会取到消息;

Topics 通配符模式:通配符这种方式是路由模式演化过来,这个将路由key设置的更灵活,就是在设置交换机和队列绑定的时候,设置路由key,表示一个单词,#表示多个单词,比如:queue. 或者#.queue# 发送消息的时候路由键queue.hehe,也就会找符合条件的队列发送过去
RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。**
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
ithub.io/flowchart.js/

可以分成两类
点对点: 一个消息只能被一个消费者消费
也就是简单模式和Work Queues
一对多: 一个消息能被多个消费者消费
发布与订阅模式,路由模式,通配符模式

解决RabbitMQ劣势的方案

1.系统可用性降低
就是当MQ出现宕机,这个可以设置MQ集群
2.系统的复杂性提高
这里最大的问题是怎么保证信息的可靠性
有三种可能:
** 1.生产者给交换机发送消息是交换机没有收到**
解决方案是确认模式confrim
生产者给交换机发送消息,交换机接收到消息后给生产者发送确认消息,反之生产者没有收到确认消息,就会重新发送

  • 确认模式:
    • 步骤:
      1. 确认模式开启:ConnectionFactory中开启publisher-confirms=“true”
      1. 在rabbitTemplate定义ConfirmCallBack回调函数
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 确认模式:
     * 步骤:
     * 1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
     * 2. 在rabbitTemplate定义ConfirmCallBack回调函数
     */
    @Test
    public void testConfirm() {
    
        //2. 定义回调 **
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *
             * @param correlationData 相关配置信息
             * @param ack   exchange交换机 是否成功收到了消息。true 成功,false代表失败
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法被执行了....");
    
                if (ack) {
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }
            }
        });
    
        //3. 发送消息
        rabbitTemplate.convertAndSend("test_exchange_confirm111", "confirm", "message confirm....");
    }
    

}

2.交换机给队列发送消息是队列没有收到
解决方案是回退模式
步骤:

    1. 开启回退模式:publisher-returns=“true”
    1. 设置ReturnCallBack
    1. 设置Exchange处理消息的模式:
    1. 如果消息没有路由到Queue,则丢弃消息(默认)
    1. 如果消息没有路由到Queue,返回给消息发送方
@Test
public void testReturn() {

  //设置交换机处理失败消息的模式
  rabbitTemplate.setMandatory(true);

  //2.设置ReturnCallBack
  rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
      /**
       *
       * @param message   消息对象
       * @param replyCode 错误码
       * @param replyText 错误信息
       * @param exchange  交换机
       * @param routingKey 路由键
       */
      @Override
      public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
          System.out.println("return 执行了....");

          System.out.println(message);
          System.out.println(replyCode);
          System.out.println(replyText);
          System.out.println(exchange);
          System.out.println(routingKey);

          //处理
      }
  });


  //3. 发送消息   
  rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
}

3.生产者将消息发送给了交换机,但是MQ宕机了,可能造成消息丢失
这个需要将交换机和队列持久化,里面有个参数durable=“true”
MQ创建集群

4.消费者拿到消息,但是在处理的过程中失败了,而队列消息已经不存在了

ack指 Acknowledge,acknowledge="manual"手动确认
这个跟上上面的用法差不多,但是是消费者处理完数据之后才发送确认消息,而不自动签收,需要手动签收

  @Component
public class AckListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            //1.接收转换消息
            System.out.println(new String(message.getBody()));

            //2. 处理业务逻辑
            System.out.println("处理业务逻辑...");
            int i = 3/0;//出现错误
            //3. 手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            //e.printStackTrace();

            //4.拒绝签收
            /*
            第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
             */
            channel.basicNack(deliveryTag,true,true);
            // 了解
            //channel.basicReject(deliveryTag,true);
        }
    }
}

RabbitMQ的高级特性

消费限流

消费限流很有必要!一般消费者拿取消息是队列有多少一次性拿取的,如果我们没有设置消费限流,当消费者系统宕机了,队列就的消息就存着,但是存着消息越来越多,消费者恢复了,这些数据一次性拿取,对于消费者来说就很吃力了,还有就是高并发情况,所以限流很有必要,可以通过MQ中的 listener-container 配置属性 perfetch = 1 , 1表示一次拿取一条数据,当消费者处理完业务手动确认后才会进行下一次拉取数据

TTL

什么是TTL?
TTL 全称 Time To Live(存活时间/过期时间)。当消息到达存活时间后,还没有被消费,会被自动清除。
两种方式:

  1. 可以在RabbitMQ管理控制台设置过期时间
  2. x-message-ttl配置这个参数 值为integer类型
 <!--ttl-->
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
    <!--设置queue的参数-->
    <rabbit:queue-arguments>
        <!--x-message-ttl指队列的过期时间  100秒-->
        <entry key="x-message-ttl" value="100000" value-type="java.lang.Integer"/>
    </rabbit:queue-arguments>
</rabbit:queue>

也可以单独设置消息的过期时间,代码实现

@Test
public void testTtl() {

  // 消息后处理对象,设置一些消息的参数信息
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {

        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            //1.设置message的信息
            message.getMessageProperties().setExpiration("5000");//消息的过期时间
            //2.返回该消息
            return message;
        }
    };

    //消息单独过期,发送消息的时候,后面多增加一个参数messagePostProcessor
    rabbitTemplate.convertAndSend("test_exchange_ttl", 
                                  "ttl.hehe", "message ttl....",messagePostProcessor);
     
    }
}

注意:如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。

  • 队列过期后,会将队列所有消息全部移除。
  • 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)

死信队列

什么是死信队列?
存放死信的队列就叫死心队列,(我知道这么解释要挨打,但是也不是没道理不是)
队列大家都知道,存放消息的
神马是死信?
死信的几种情况??
1.消息过期了且还未被消费的消息;
2.消息被拒收了,且拒收的时候给的requeue=false 就是不让返回原本队列的消息
3.队列已满,存不下的消息也会成为死信
而这些消息都被成为死信;
怎么讲死信消息存放进死信队列中呢??
正常的队列绑定死信交换机,当这个队列出现死信消息时就会自动发送给死信交换机,然后存入死信队列中去;

要如何绑定死信交换机
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key

<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
  <!--3. 正常队列绑定死信交换机-->
  <rabbit:queue-arguments>
      <!--3.1 x-dead-letter-exchange:死信交换机名称-->
      <entry key="x-dead-letter-exchange" value="exchange_dlx" />

      <!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
      <entry key="x-dead-letter-routing-key" value="dlx.hehe" />

      <!--4.1 设置队列的过期时间 ttl-->
      <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
      <!--4.2 设置队列的长度限制 max-length -->
      <entry key="x-max-length" value="10" value-type="java.lang.Integer" />
  </rabbit:queue-arguments>
</rabbit:queue>
      <!--下面是死信交换机跟死信队列的绑定 ttl-->
<rabbit:topic-exchange name="test_exchange_dlx">
  <rabbit:bindings>
      <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
  </rabbit:bindings>
</rabbit:topic-exchange>

死信交换机和死信队列和普通的没有区别

延迟队列

延迟队列就是消息不会立马就被消费者消费,只有满足指定的时间后才会被消费者消费,
但是RabbitMQ并没有这个功能,但是可以是TTL+死信队列结合形成延迟队列
设置过期时间,不给他绑定消费者,那么这个消息就会成为死信,但是成为死信不代表这个消息不在不好不能用,它还是一条消息,这时候消费者区死信队列中拿取数据进行业务处理
这么说可能比较蒙,设置一个应用场景:
抢购30分钟后如果没有支付,库存回滚,用户不支付,不代表其他人不买,
订单系统收到用户的下单,然后订单系统在他的数据库增加了订单,但是未付款,订单系统给MQ发送订单消息,消息30分钟后被存如了死信队列,消费者监听到了消息,通过这个消息去数据库查询订单状态,如果还未付款,就库存回滚,
还有很多场景都是需要这种延迟队列的

RabbitMQ应用问题

消息可靠性保障

如何保证消息的100%发送成功
百度有很多这种导图
生产者发送数据之前,会先执行它的业务,存入它的数据库,然后发送消息给MQ,消费者拿到消息,执行完成后也会存一份到他的数据库,然后返回ack,如果消费者处理业务失败也就没有返回ack,这时候生产者就会重新发送数据,但是如果在生产者发送给MQ时就出现问题,生产者根本没有发送成功,可以做一个定时器,做一个效验系统,定时每分钟执行比较生产者的数据库和消费者处理完成的数据库,如果出现不同,表示有消息没有处理,然后将没有处理的消息在发送给MQ,让消费者接收消息接着处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值