【RocketMQ学习笔记篇一】

在这里插入图片描述

一、MQ的概念:

消息模型:
  • 🍋RocketMQ主要由 ProducerBrokerConsumer 三部分组成 其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 BrokerMessage Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。
  • 🍋Producer:
    • 负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。
  • 🍋Consumer:
    • 负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费、推动式消费。
  • 🍋Broker:
    • 负责存储消息,生产者发送的消息存储在这里而这里则是由多个队列组成,主要是担任中转、代理的角色。

在这里插入图片描述

MQ的优缺点:

优点:

  • 🥭应用解耦,客户端不直接和服务端打交道
  • 🥭适应应用的快速变更,服务器数量的变更
  • 🥭流量削峰,突发的流量不会直接打到服务端

缺点:

  • 🥭系统可用性降低,一旦 MQ 挂掉客户端发送不了消息和服务端拉去不到消息
  • 🥭系统负责度提高,增加了一项技术自然复杂度提高了
  • 🥭异步消息机制
    • 1️⃣ 消息的顺序性
    • 2️⃣ 消息的丢失
    • 3️⃣ 消息的一致性
    • 4️⃣ 消息重复使用
MQ的产品分类:
名称开发语言吞吐量处理时间架构亮点
ActiveMQJava万级ms级主从成熟度高
RabbitMQErlang万级us级主从性能更好
RocketMQJava十万级ms级分布式扩展性强
kafkaScala十万ms级分布式多应用于大数据

👉RocketMQ完美解决MQ的缺点

二、环境搭建:

🍂下载:

🔗下载地址:https://rocketmq.apache.org/docs/quick-start/

在这里插入图片描述

🍂版本支持:

在这里插入图片描述

🍂安装:
  • JDK1.8 我想这是必要条件

  • 上传到Liunx 后解压即可

  • 启动Name Server

     nohup sh bin/mqnamesrv &
     tail -f ~/logs/rocketmqlogs/namesrv.log
     The Name Server boot success...
    
  • 启动Broker

     nohup sh bin/mqbroker -n localhost:9876 &
     tail -f ~/logs/rocketmqlogs/broker.log 
     The broker[%s, 172.30.30.233:10911] boot success...
    
  • 如果你在启动的时候出现这个错:

在这里插入图片描述

  • 那么你需要配置下JVM的给与Broker的内存大小, /bin/runbroker.sh

在这里插入图片描述

三、消息发送:

1️⃣ 导入客户端依赖
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.9.3</version>
</dependency>
2️⃣ 编写生成者Producer
public class Producer {
    public static void main(String[] args) throws Exception {

        // 1.创建mq对象
        DefaultMQProducer producer = new DefaultMQProducer();
        //2.设置组
        producer.setProducerGroup("group");
        //3.设置nameServer的地址
        producer.setNamesrvAddr("localhost:9876");
        //4.开启发送
        producer.start();
        //5.创建消息对象
        Message message = new Message("topic1","发送的消息".getBytes(StandardCharsets.UTF_8));
        //6.发送消息
        SendResult sendResult = producer.send(message); //指定超时时间
        System.out.println(sendResult);
        //7.关闭连接
        producer.shutdown();

    }
}
3️⃣ 编写消费者Consumer
public class Consumer {
    public static void main(String[] args) throws MQClientException {
        // 1.创建mq对象
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group");
        //2.设置nameServer的地址
        consumer.setNamesrvAddr("localhost:9876");
        //3.设置topic以及flag
        consumer.subscribe("topic1","*");
        //4.注册一个监听器
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println("接收到的消息:" + new String(messageExt.getBody()));
                    System.out.println(messageExt);
                }

                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //.开启发送
        consumer.start();
    }
}

🍀如果你启动Producer 报如下错误,你只需要在发送消息的时候指定超时时间即可

在这里插入图片描述

🍀 如果任然出现和我一样的错,并且你使用的是云服务器,那么你需要开放9876和10911端口,如果你使用的虚拟机那么你应该关闭防火墙

在这里插入图片描述

🍀 如果你发现启动broker的时候ip发生了变化,那么你可以固定你想要的ip来进行启动

在这里插入图片描述

🍀然后启动broker时输入:
nohup ./mqbroker -n 自己指定的ip:9876 -c …/conf/broker.conf & 即可

发送消息的类型

  • 同步消息:上面的例子便是同步。
  • 异步消息
 public class AsyncSendMessage {
          public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException {
              // 1.创建mq对象
              DefaultMQProducer producer = new DefaultMQProducer();
              //2.设置组
              producer.setProducerGroup("group");
              //3.设置nameServer的地址
              producer.setNamesrvAddr("localhost:9876"); //指定自己服务器的ip
              //4.开启发送
              producer.start();
              // 异步发送失败的重试次数
              producer.setRetryTimesWhenSendAsyncFailed(0);
              //5.创建消息对象
              Message message = new Message("topic1","发送的消息".getBytes(StandardCharsets.UTF_8));
              //6.发送消息
              int messageCount = 5;
             final CountDownLatch2 downLatch2 = new CountDownLatch2(messageCount);
              for (int i = 0; i < messageCount; i++) {
                  final int index = i;
                  producer.send(message, new SendCallback() {
                      //发送成功
                      @Override
                      public void onSuccess(SendResult sendResult) {
                          downLatch2.countDown();
                          System.out.printf("%-1d 发送成功 %s %n",index,sendResult.getMsgId());
                      }
                      // 出现异常
                      @Override
                      public void onException(Throwable throwable) {
                          downLatch2.countDown();
                          System.out.printf("%-1d Exception %s %n", index, throwable);
                          throwable.printStackTrace();
                      }
                  });
              }
              // 等待5s
              downLatch2.await(5, TimeUnit.SECONDS);
              // 如果不再发送消息,关闭Producer实例。
              producer.shutdown();
          }
      }

🍀接收消息同步异步并无区别

  • 单向消息
public class OnewayProducer {
      	public static void main(String[] args) throws Exception{
          	// 实例化消息生产者Producer
              DefaultMQProducer producer = new DefaultMQProducer("group_name");
          	// 设置NameServer的地址
              producer.setNamesrvAddr("localhost:9876");
          	// 启动Producer实例
              producer.start();
          	for (int i = 0; i < 5; i++) {
              	// 创建消息,并指定Topic,Tag和消息体
              	Message msg = new Message("TopicTest","TagA",
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
              	);
              	// 发送单向消息,没有任何返回结果
              	producer.sendOneway(msg);
          	}
          	// 如果不再发送消息,关闭Producer实例。
          	producer.shutdown();
          }
      }
延时消息:
   // 设置延时等级3,这个消息将在10s之后发送
      message.setDelayTimeLevel(3);
      // 发送消息
      producer.send(message);
  // 延时消息分为18个等级,如果level==0,则不延迟
      private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
批量发送消息:
  • 批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,相同的waitStoreMsgOK,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB。
   String topic = "BatchTest";
      List<Message> messages = new ArrayList<>();
      messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes()));
      messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes()));
      messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes()));
      try {
         producer.send(messages);
      } catch (Exception e) {
         e.printStackTrace();
         //处理error
      }
消息的过滤:
  • tag 标签过滤
  //创建消息对象
      Message message = new Message("topic1","tag","发送的消息".getBytes(StandardCharsets.UTF_8));
      //发送消息
      SendResult sendResult = producer.send(message,10000);
      consumer.subscribe("topic1","tag || taga || tagb"); // 可以使用 ||符号
              //4.注册一个监听器
              consumer.registerMessageListener(new MessageListenerConcurrently() {
                  @Override
                  public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                      return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});
              //.开启发送
              consumer.start();
  • sql 过滤

    • 基本语法
    • RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。
      • 数值比较,比如:>>=<<=BETWEEN=
      • 字符比较,比如:=<>IN
      • IS NULL 或者 IS NOT NULL
      • 逻辑符号 ANDORNOT
        常量支持类型为:
      • 数值,比如:123,3.1415;
      • 字符,比如:'abc',必须用单引号包裹起来;
      • NULL,特殊的常量
      • 布尔值,TRUEFALSE

    🍀只有使用push模式的消费者才能用使用SQL92标准的sql语句,所以我们需要开启对sql的支持,修改conf/broker.conf文件

    在这里插入图片描述

  • Producer

      //创建消息对象
      Message message = new Message("topic1","发送的消息".getBytes(StandardCharsets.UTF_8));
      // 设置一些属性
      msg.putUserProperty("a", "19");
  • consumer
      //设置topic以及flag
      consumer.subscribe("topic1", MessageSelector.bySql("a >= 18"));
顺序发送消息:
  • 比如根据某个对象的id进行分类,选择不同的消息队列
 SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                     @Override
                     public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                         Long id = (Long) arg;  //根据订单id选择发送queue
                         long index = id % mqs.size();
                         return mqs.get((int) index);
                     }
                 }, orderList.get(i).getOrderId());//订单id
  • 取队列中的消息需要变更原来的监听器
/**
  * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
  * 如果非第一次启动,那么按照上次消费的位置继续消费
  */
      consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
      consumer.registerMessageListener(new MessageListenerOrderly() {
      
                 Random random = new Random();
                 @Override
                 public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                     context.setAutoCommit(true);
                     for (MessageExt msg : msgs) {
                         // 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序
                         System.out.println("consumeThread=" + Thread.currentThread().getName() + "queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody()));
                     }
                     try {
                         //模拟业务逻辑处理中...
                         TimeUnit.SECONDS.sleep(random.nextInt(10));
                     } catch (Exception e) {
                         e.printStackTrace();
                     }
                     return ConsumeOrderlyStatus.SUCCESS;
                 }
             });

🍀由于消费者在消费消息时,只能单线程消费消息那么效率毫无疑问肯定会下降,我想这一点是你需要知道的。

事物发送消息:
  • 事物的状态:
    • 🌻 TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息。
    • 🌻 TransactionStatus.RollbackTransaction : 回滚事务,它代表该消息将被删除,不允许被消费。
    • 🌻 TransactionStatus.Unknown : 中间状态,它代表需要检查消息队列来确定状态。
  • Producer
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
      import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
      import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
      import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
      import org.apache.rocketmq.common.message.MessageExt;
      import java.util.List;
      public class TransactionProducer {
         public static void main(String[] args) throws MQClientException, InterruptedException {
             TransactionListener transactionListener = new TransactionListenerImpl();
             TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
             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();
             String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
             for (int i = 0; i < 10; i++) {
                 try {
                     Message msg =
                         new Message("TopicTest1234", tags[i % tags.length], "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 (MQClientException | UnsupportedEncodingException e) {
                     e.printStackTrace();
                 }
             }
             for (int i = 0; i < 100000; i++) {
                 Thread.sleep(1000);
             }
             producer.shutdown();
         }
      }
  • Consumer
public class TransactionListenerImpl implements TransactionListener {
        private AtomicInteger transactionIndex = new AtomicInteger(0);
        private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
        @Override
        public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            int value = transactionIndex.getAndIncrement();
            int status = value % 3;
            localTrans.put(msg.getTransactionId(), status);
            return LocalTransactionState.UNKNOW;
        }
        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt msg) {
            Integer status = localTrans.get(msg.getTransactionId());
            if (null != status) {
                switch (status) {
                    case 0:
                        return LocalTransactionState.UNKNOW;
                    case 1:
                        return LocalTransactionState.COMMIT_MESSAGE;
                    case 2:
                        return LocalTransactionState.ROLLBACK_MESSAGE;
                }
            }
            return LocalTransactionState.COMMIT_MESSAGE;
        }
      }
  • 事物的注意点:

    1. 事务消息不支持延时消息和批量消息。
    1. 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次,但是用户可以通过 Broker 配置文件的 transactionCheckMax参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = transactionCheckMax ) 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写 AbstractTransactionalMessageCheckListener 类来修改这个行为。
    1. 事务消息将在 Broker 配置文件中的参数 transactionTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 transactionTimeout 参数。
    1. 事务性消息可能不止一次被检查或消费。
    1. 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。
    1. 事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值