2-rocketmq-message sending and receiving

quick start

add dependencies

<dependency>
  <groupId>org.apache.rocketmq</groupId>
  <artifactId>rocketmq-client</artifactId>
  <version>4.7.1</version>
</dependency>

producer

public class Producer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        /**
         * 生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组rocketmq支持事务消息,在发送事务消息时,如果事务消息异常(producer挂了),broker端会来回查事务的状态,这个时候会根据group名称来查找对应的producer来执行相应的回查逻辑。相当于实现了producer的高可用
         */
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        // namesrv地址 多个地址用 ; 隔开  从namesrv上拉取broker信息
        producer.setNamesrvAddr("localhost:9876");
        producer.start();
        for (int i = 0; i < 1000; i++) {
            try {
              	/**
              	 * 创建消息实例,指定topic,tag,消息内容。tag
              	 */
                Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );
                // 发送消息并获取发送结果   同步发送
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }
}

In SendResult, there is a sendStatus status, indicating the sending status of the message. There are four states


  1. FLUSH_DISK_TIMEOUT: Indicates that the flash disk was not completed within the specified time ( this error will only be reported if the Broker’s disk flush policy Ill is set to SYNC_FLUSH).
  2. FLUSH_SLAVE_TIMEOUT: Indicates that in master-standby mode, and Broker is set to SYNC_MASTER mode,
    the master-slave synchronization has not been completed within the set time.
  3. SLAVE_NOT_AVAILABLE: The scenario generated by this state is similar to FLUSH_SLAVE_TIMEOUT, which means that it is in active/standby mode
    , and Broker is set to SYNC_MASTER, but no Broker configured as Slave can be found.
  4. SEND OK: Indicates that the sending is successful, and the specific meaning of the sending success, such as whether the message has been stored to the disk? Has the message been
    synchronized to the Slave? Are messages written to disk on the slave? It needs to be determined in combination with the configured brushing strategy and master-slave strategy
    . This state can also be simply understood as SEND OK if the three problems listed above do not occur.

consumer

public class Consumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        //groupName 将多个consumer分组,提高并发处理能力。需要和MessageModel配合
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
        // 多个地址 ;分开 获取broker地址 并定时向broker发送心跳 可以从master/slave获取订阅
        consumer.setNamesrvAddr("localhost:9876");
        // 两种消息模式  BROADCASTING   CLUSTERING
        consumer.setMessageModel(MessageModel.BROADCASTING);
        //设置consumer第一次启动从队列头部还是尾部开始消费
      	//如果非第一次启动,那么按上一次消费的位置继续消费(取决于本地的offeset数据)
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        // topic 可通过tag过滤消息  * 或 null 代表全部
        consumer.subscribe("TopicTest", "*");
        /**注册消息处理回调
         * MessageListenerConcurrently 普通监听
         * MessageListenerOrderly 顺序监听
         */
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                // todo 消息处理逻辑
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
              	// 返回消费状态
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        // 启动consumer
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

consumption status

ConsumeConcurrentlyStatus {
  	// 消费成功
    CONSUME_SUCCESS,
  // 使用失败,稍后尝试使用
    RECONSUME_LATER;

Basic principles of message sending and consumption

Cluster deployment, a master can have multiple slaves, and a slave can only have one master.consumer can subscribe to messages from the master receiver slave

2m-2s example:

image-20200709175842041

RocketMQ does not implement master election (specify master-slave through configuration files)

When the master hangs up, consumers can still consume messages normally (slave provides read services)

Partitioning through groupName to improve the processing capacity of consumers

consumer

Two types of consumers

  • DefaultMQPushConsumer read operations controlled by the system

DefaultMQPushConsumer

Automatically save offset, automatic load balancing

public class Consumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        //groupName 将多个consumer分组,提高并发处理能力。需要和MessageModel配合
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
        // 多个地址 ;分开
        consumer.setNamesrvAddr("localhost:9876");
        // 两种消息模式  BROADCASTING   CLUSTERING
        consumer.setMessageModel(MessageModel.BROADCASTING);
        //第一次启动从 offset头开始
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        // topic 可通过tag过滤消息  * 或 null 代表全部
        consumer.subscribe("TopicTest", "*");
        //注册消息处理回调
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                // todo 消息处理逻辑
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        // 启动consumer
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

两种消息模式 BROADCASTING CLUSTERING:

1、在 Clustering 模式下,同一个 ConsumerGroup ( GroupName 相同 ) 里的每个 Consumer 只消费所订阅消息 的一部分 内 容, 同一个 ConsumerGroup里所有的 Consumer 消 费 的内 容合起来才是所订阅 Topic 内 容 的 整体 ,从而达到负载均衡的目的 (也就是集群消费)

2、在 Broadcasting 模式下,同一个 ConsumerGroup 里的每个 Consumer 都能消费到所订阅 Topic 的全部消息,也就是一个消息会被多次分发,被多个Consumer 消费 。(也就是广播模式)

image-20200709181438549

通过长轮询的方式获取消息

Broker端HOLD住客户端过来的请求一小段时间,在这个时间内有新消息到达就利用现有的连接立刻返回消息给Consumer。主动权在Consumer

好处是客户端能充分利用资源,不至于处理不过来

流量控制

DefaultMQPullConsumer

需要自己维护offset,需要通过遍历MessageQueue获取消息

public class PullConsumer {
    private static final Map<MessageQueue, Long> OFFSE_TABLE = new HashMap<MessageQueue, Long>();

    public static void main(String[] args) throws MQClientException {
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("please_rename_unique_group_name_5");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.start();
        // 获取分片
        Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("broker-a");
        for (MessageQueue mq : mqs) {
            System.out.printf("Consume from the queue: %s%n", mq);
            SINGLE_MQ:
            while (true) {
                try {
                    PullResult pullResult =
                        consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32);
                    System.out.printf("%s%n", pullResult);
                    putMessageQueueOffset(mq, pullResult.getNextBeginOffset());
                    switch (pullResult.getPullStatus()) {
                        // 获取到消息
                        case FOUND:
                            break;
                        case NO_MATCHED_MSG:
                            break;
                        // 没有新消息
                        case NO_NEW_MSG:
                            break SINGLE_MQ;
                        case OFFSET_ILLEGAL:
                            break;
                        default:
                            break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        consumer.shutdown();
    }

    private static long getMessageQueueOffset(MessageQueue mq) {
        Long offset = OFFSE_TABLE.get(mq);
        if (offset != null)
            return offset;

        return 0;
    }

    private static void putMessageQueueOffset(MessageQueue mq, long offset) {
        OFFSE_TABLE.put(mq, offset);
    }

}

Consumer的启动、关闭

DefaultMQPushConsumer启动时不会检查nameServer地址的正确或者可用性

// 从指定topic中拉取所有消息队列
Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("order-topic");

可以通过上面的方法主动拉取消息队列来判断nameServer的可用性

关闭时调用shutdown()即可

DefaultMQPullConsumer关闭或者异常退出时需要将offset保存起来

才能保证下次启动时拉取消息的正确性

consumerGroup:位于同一个consumerGroup中的consumer实例和producerGroup中的各个produer实例承担的角色类似;同一个group中可以配置多个consumer,可以提高消费端的并发消费能力以及容灾
和kafka一样,多个consumer会对消息做负载均衡,意味着同一个topic下的不messageQueue会分发给同一个group中的不同consumer

消费端的负载均衡

和kafka一样,消费端也会针对Message Queue做负载均衡,使得每个消费者能够合理的消费多个分区的消息。

消费端会通过RebalanceService线程,10秒钟做一次基于topic下的所有队列负载
  • 消费端遍历自己的所有topic,依次调rebalanceByTopic

  • 根据topic获取此topic下的所有queue

  • 选择一台broker获取基于group的所有消费端(有心跳向所有broker注册客户端信息)

  • 选择队列分配策略实例AllocateMessageQueueStrategy执行分配算法

什么时候触发负载均衡
  • 消费者启动之后
  • 消费者数量发生变更
  • 每10秒会触发检查一次rebalance
分配算法

RocketMQ提供了6中分区的分配算法

  • (AllocateMessageQueueAveragely)平均分配算法(默认)
  • (AllocateMessageQueueAveragelyByCircle)环状分配消息队列
  • (AllocateMessageQueueByConfig)按照配置来分配队列: 根据用户指定的配置来进行负载
  • (AllocateMessageQueueByMachineRoom)按照指定机房来配置队列
  • (AllocateMachineRoomNearby)按照就近机房来配置队列:
  • (AllocateMessageQueueConsistentHash)一致性hash,根据消费者的cid进行

生产者

DefaultMQProducer 默认生产者

public class Producer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        // producerGroupName
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        // namesrv地址 多个地址用 ; 隔开
        producer.setNamesrvAddr("localhost:9876");
        producer.start();
        for (int i = 0; i < 1000; i++) {
            try {
                Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );
                // 返回 
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }
}

消息返回状态:SendResult.sendStatus

1、FLUSH DISK TIMEOUT : 表示没有在规定时间内完成刷盘(需要Broker 的刷盘策略设置成 SYNC FLUSH 才会报这个错误) 。
2、 FLUSH SLAVE TIMEOUT :表示在主备方式下,并且 Broker 被设置成 SYNC MASTER 方式,没有在设定时间内完成主从同步 。
3、SLAVE NOT AVAILABLE : 这个状态产生的场景和 FLUSH SLAVETIMEOUT 类似, 表示在主备 方式下,并且 Broker 被设置成 SYNCMASTER ,但是没有找到被配置成 S lave 的 Broker 。
4、SEND OK :表示发送成功,发送成功的具体含义,比如消息是否已经被存储到融盘?消息是否被同步到了 S lave 上?消息在 S lave 上是否被写人磁盘?需要结合所配置的刷盘策略、主从策略来定 。 这个状态还可以简单理解为,没有发生上面列出的三个问题状态就是 SEND OK

延迟消息

通过Message.setDelayTimeLevel ( int level ) 方法设置延迟时间,只支持预设值(1s/5s/1Os/30s/Im/2m/3m/4m/5m/6m/7m/8m/9m/1 Om/20m/30m/1 h/2h )。 比如setDelayTimeLevel(3)表示延迟 10s 。

自定义消息发送规则

实现MessageQueueSelector接口
三种默认实现:
SelectMessageQueueByHash
SelectMessageQueueByMachineRoom
SelectMessageQueueByRandom

自定义消息发送可以将消息发送到指定的MessageQueue里

对事物的支持

new TransactionMQProducer("groupName");

设置生产者group,当一个producer挂掉了,消息会分发到其它producer保证消息一定会被回查确定

消息的可靠性原则

只有消费者返回CONSUME_SUCCESS消费成功的才会认为消费成功

返回ConsumeConcurrentlyStatus.RECONSUME_LATER消费失败会被重试

消息衰减重试

为了保证消息肯定至少被消费一次,RocketMQ会把这批消息重新发回到broker,在延迟的某个时间点
(默认是10秒,业务可设置)后,再次投递到这个ConsumerGroup。而如果一直这样重复消费都持续
失败到一定次数(默认16次),就会投递到DLQ死信队列。应用可以监控死信队列来做人工干预
可以修改broker-a.conf文件
messageDelayLevel = 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

重试消息的处理机制

一般情况下我们在实际生产中是不需要重试16次,这样既浪费时间又浪费性能,理论上当尝试重复次数
达到我们想要的结果时如果还是消费失败,那么我们需要将对应的消息进行记录,并且结束重复尝试

consumer.registerMessageListener((MessageListenerConcurrently) (list,
  consumeOrderlyContext) -> {
                for (MessageExt messageExt : list) {
                    if(messageExt.getReconsumeTimes()==3) {
                     //可以将对应的数据保存到数据库,以便人工干预	
                       System.out.println(messageExt.getMsgId()+","+messageExt.getBody());
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    }
                } r
                eturn ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            });

死信队列

RocketMQ会为每个消费组都设置一个Topic命名为“%DLQ%+consumerGroup"的死信队列

Guess you like

Origin blog.csdn.net/m0_53121042/article/details/112344326