RabbitMQ

1.集群模式

1.1 普通集群模式

创建的queue,只会放在一个rabbtimq实例上,每个RabbitMQ实例都同步queue的元数据。读取时访问到了实际数据不在的实例,它会从queue所在实例上拉取数据过来。
没有高可用性。
在这里插入图片描述

1.2 镜像集群

创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。
没有扩展性。
在这里插入图片描述

2. RabbitMQ 防止消息丢失

2.1 消息持久化

2.2 消费者开启手动ACK

2.3 使用镜像集群

2.4 消息补偿机制

记录消息发送日志,消息接收日志检查消息是否成功消费,未成功消费进行消息补偿。

3. 如何保障消息不重复消费

生产者向中间价发送消息的时候会生成一个唯一的id。
使用key通过Redis或数据库唯一索引保证消费接口得幂等性。

4.保证消息顺序消费

  1. 消息划分为多个queue,每个消费者顺序消费一个queue的信息。
  2. 一个queue,一个消费者,消费时根据消息根据消息关键值比如说订单id分为多个内存队列,一个线程负责消费一个内存队列。

5. 生产者发送的消息如何保证成功发送到消息队列中

5.1 事务方式

事务方式:amqp协议提供的一种保证消息成功投递的方式;
通过将信道开启 transactional 模式,并利用信道 Channel 的三个方法来实现以事务方式发送消息,若发送失败,通过异常处理回滚事务,确保消息成功投递。

  1. channel.txSelect(): 开启事务
  2. channel.txCommit() :提交事务
  3. channel.txRollback() :回滚事务

示例:

 try {
         channel.txSelect();
         channel.basicPublish("x-hello", "test", true, MessageProperties.PERSISTENT_BASIC, ("第" + (i + 1) + "条消息").getBytes());

         channel.txCommit();
     } catch (Exception e) {
         // 发生异常,说明消息没有到达broker的queue中,回滚。
         try {
             log.error("提交事务失败,事务回滚 i = " + i);
             channel.txRollback();
         } catch (IOException e1) {
             log.error("mq broker error...");
         }
         log.error("mq broker error...");
     }

5.2 发布确认方式

// 开启信道为确认模式
channel.confirmSelect();
确认模式又分为同步等待mq服务器确认和异步等待确认两种

  1. 异步监听等待 channel.addConfirmListener(listener)
  // 开启confirm模式, 模拟发送一千条消息,记录总耗时
  rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
      String correlationId = message.getMessageProperties().getCorrelationIdString();
      log.debug("消息:{} 发送失败, 应答码:{} 原因:{} 交换机: {}  路由键: {}", correlationId, replyCode, replyText, exchange, routingKey);
  });

  // 获取连接工厂
  rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> {
      System.out.println("=====消息确认回调了======");
      if (ack) {
          System.out.println("消息id为: " + correlationData + "的消息,已经被ack成功");
      } else {
          System.out.println("消息id为: " + correlationData + "的消息,消息nack,失败原因是:" + cause);
      }
  });

  // 开启连接 - tcp连接
  // 准备发送一万条测试消息
  long start = System.currentTimeMillis();
  for (int i = 0; i < 1000; i++) {
      rabbitTemplate.convertAndSend("x-hello", "test", ("第" + (i + 1) + "条消息").getBytes(), new CorrelationData(String.valueOf(i)));
  }
  System.out.println("消息确认 - 异步确认,1000条消息发送共耗时: " + (System.currentTimeMillis() - start) + "ms");
  1. 同步等待 channel.waitForConfirms()
      @Test
      public void testAsynMode() {
          // 获取连接工厂
          ConnectionFactory connectionFactory =         
          rabbitTemplate.getConnectionFactory();

          // 开启连接 - tcp连接
          Connection connection = connectionFactory.createConnection();

          // 建立信道 构造参数 true代表该信道开启 Transactional 事务模式, false 代表为非事务模式
          Channel channel = connection.createChannel(false);

          long start = System.currentTimeMillis();
          for (int i = 0; i <= 10000; i++) {
              try {
                  // 开启发布确认模式
                  channel.confirmSelect();

                  channel.basicPublish("x-hello", "test", true, MessageProperties.PERSISTENT_BASIC, ("第" + (i + 1) + "条消息").getBytes());

                  // 阻塞方法,直到mq服务器确认消息
                  if (channel.waitForConfirms()) {
                      log.info("消息发送成功");
                  }
              } catch (Exception e) {
                  // 发生异常,说明消息没有到达broker的queue中,回滚。
                  log.error("mq broker error...");
              }
          }
          System.out.println("发送确认 - 同步确认提交下,10000条消息发送共耗时: " + (System.currentTimeMillis() - start) + "ms");

6. RabbitMq消费者获取消息有两种模式

  1. push模式,又称为Subscribe订阅方式,使用监听器等待数据。
  2. pull模式,又称为poll API方式(轮询方式),从rabbitmq服务器中手动读取数据。

优缺点:
3. 消费端可以根据自身消费能力决定是否pull;
4. 实时性依赖于pull间隔时间,间隔越大,实时性越低。push实时性非常好;
5. push将消息提前推送给消费者,消费者必须设置一个缓冲区缓存这些消息。优点是消费者总是有一堆在内存中待处理的消息,所以消费消息时效率很高。缺点就是缓冲区可能会溢出。

1.RocketMQ物理部署结构

在这里插入图片描述

RocketMQ 网络部署特点

  • Name Server 是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。

  • Broker 部署相对复杂,Broker 分为 Master 和 Slave,一个 Master 可以对应多个 Slave,但是一个 Slave 只能对应一个Master,Master与 Slave的对应关系通过指定相同的BrokerName,不同同的BrokerId来定义,BrokerId为 0 表示 Master,非 0 表示 Slave。Master 也可以部署多个。每个 Broker 与Name Server 集群中的所有节点建立长连接,定时注册 Topic 信息到所有 Name Server。

  • Producer 与 Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从 Name Server 获取 Topic 路由信息,并向提供 Topic 服务的 Master 建立长连接,且定时向 Master 发送心跳。Producer 完全无状态,可集群部署。

  • Consumer 与 Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从 Name Server 取 Topic 路由信息,并向提供 Topic 服务的 Master、Slave 建立长连接,且定时向 Master、Slave 发送心跳。Consumer既可以从 Master 订阅消息,也可以从 Slave 订阅消息,订阅规则由 Broker 配置决定。

可以从上面的出以下关键点:

  1. Name Server是一个关于Topic的网络路由管理器,它存放和管理所有的Topic。所有的Broker都要定时注册Topic信息到路由中。
  2. 所有的Producer和Consumer都不是直接与MQ(Broker)进行交互的。它需要先根据自己的Topic到Name Server中获取该Topic对应哪台Broker。拿到路由信息,再进行交互。
  3. Name Server之间不进行数据通信,与Zookeeper不同之处体现出来了

2.事务消息

实现原理

  1. 投递消息:Producer向Broker投递一个事务消息,并且带有唯一的key作为参数(幂等性)
  2. Broker预提交消息(在Broker本地做了存储,但是该消息的状态对Consumer不可见)
  3. Broker预提交成功后回调Producer的executeLocalTransaction方法
  4. Producer提交业务(比如记录最终成功投递的日志),并根据业务提交的执行情况,向Broker反馈Commit 或者回滚
  5. Broker最终处理
  • Broker监听到Producer发来的Commit反馈时,会最终提交这个消息到本地,此时该事务消息对Consumer可见,事务消息最终投递成功,事务结束
  • Broker监听到Producer发来的RollBack反馈时,会最终回滚掉本地的预提交的消息,事务消息最终投递失败,事务结束
  • Broker超时未接受到Producer的反馈,会定时重试调用Producer.checkLocalTransaction,Producer会根据自己的执行情况Ack给Broker

Ack消息的3种状态:

  1. Broker是根据Producer发送过来的状态码,来决定下一步的操作(提交、回滚、重试)
  2. TransactionStatus.CommitTransaction: commit transaction,it means that allow consumers to consume this message.
  3. TransactionStatus.RollbackTransaction: rollback transaction,it means that the message will be deleted and not allowed to consume.
  4. TransactionStatus.Unknown: intermediate state,it means that MQ is needed to check back to determine the status.

Producer实现2个接口方法:
实际上官方的这种模式,重试指的是check的重试而不是execute的重试,因为execute方法只会执行一次,要特别注意。

  • execute:最终执行本地事务,并Ack执行状态给Broker
  • check:检查Producer是否成功执行了事务,并Ack执行状态给Broker
    实际上是可以写在一个方法里面的,execute的时候先根据key进行check,已经执行了就Ack OK,没有的话就执行。执行成功Ack Ok,执行失败就Ack RollBack。
    但是这里官方把这个功能拆分的更细了,降低单一方法的复杂度

2.1 Producer代码:

//引用:https://www.jianshu.com/p/84d830bee587
        TransactionListener transactionListener = new TransactionListenerImpl();
        TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
        producer.setNamesrvAddr("192.168.29.129:9876");
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });

        producer.setExecutorService(executorService);
        producer.setTransactionListener(transactionListener);
        producer.start();

        for (int i = 0; i < 10; i++) {
            try {
                Message msg =new Message("TopicTest", "TagA", "KEY" + i,("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.sendMessageInTransaction(msg, null);
                System.out.printf("%s%n", sendResult);
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

2.2 事务监听器代码

public class TransactionListenerImpl implements TransactionListener {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String msgBody;
        //执行本地业务的时候,再插入一条数据到事务表中,供checkLocalTransaction进行check使用,避免doBusinessCommit业务成功,但是未返回Commit
        try {
            msgBody = new String(msg.getBody(), "utf-8");
            doBusinessCommit(msg.getKeys(),msgBody);
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (UnsupportedEncodingException e) {
             e.printStackTrace();
             return LocalTransactionState.ROLLBACK_MESSAGE;
    
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        Boolean result=checkBusinessStatus(msg.getKeys());
        if(result){
            return LocalTransactionState.COMMIT_MESSAGE;    
        }else{
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        
    }
    
    public static void doBusinessCommit(String messageKey,String msgbody){
        System.out.println("do something in DataBase");
        System.out.println("insert 事务消息到本地消息表中,消息执行成功,messageKey为:"+messageKey);
    }
    
    public static Boolean checkBusinessStatus(String messageKey){
        if(true){
            System.out.println("查询数据库 messageKey为"+messageKey+"的消息已经消费成功了,可以提交消息");
            return true;
        }else{
            System.out.println("查询数据库 messageKey为"+messageKey+"的消息不存在或者未消费成功了,可以回滚消息");
            return false;
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值