RocketMQ

RocketMQ

准备工作

下载RocketMQ

这次下载rocketMQ通过docker安装,

  1. 先安装相应版本的jdk,我暂时安装的是jdk1.8,
  2. 使用docker 将rocketMQ的镜像给爬下来
docker pull rocketmqinc/rocketmq

​ 3. 启动namesrv服务

docker run -d -p 9876:9876 -v {RmHome}/data/namesrv/logs:/root/logs -v {RmHome}/data/namesrv/store:/root/store --name rmqnamesrv -e "MAX_POSSIBLE_HEAP=100000000" rocketmqinc/rocketmq sh mqnamesrv

​ 4. 启动broker服务

brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = {docker宿主机IP}
  1. 启动服务
docker run -d -p 10911:10911 -p 10909:10909 -v  {RmHome}/data/broker/logs:/root/logs -v  {RmHome}/rocketmq/data/broker/store:/root/store -v  {RmHome}/conf/broker.conf:/opt/rocketmq/conf/broker.conf --name rmqbroker --link rmqnamesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -e "MAX_POSSIBLE_HEAP=200000000" rocketmqinc/rocketmq sh mqbroker -c /opt/rocketmq/conf/broker.conf

  1. 安装控制台
docker pull styletang/rocketmq-console-ng  
  1. 启动该容器
docker run -e "JAVA_OPTS=-Drocketmq.config.namesrvAddr={docker宿主机ip}:9876 -Drocketmq.config.isVIPChannel=false" -p 8080:8080 -t styletang/rocketmq-console-ng
  1. 输入网址,查看控制台

image-20201109150217363

测试RocketMQ

发送消息

#设置环境变量
export NAMESRV_ADDR=localhost:9876
# 使用按爪个包的demo发送消息
# sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer

接受消息

# 设置环境变量
export NAMESRV_ADDR=localhost:9876
# 接受消息
# sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

关闭RocketMQ

# 1.关闭NAMESERVER
sh bin/mqshutdown namesrv
# 2.关闭broker 

集群搭建

image-20201109152654769

角色介绍

  • Producer:消息的发送者,
  • Consumer:消息接收者
  • Broker:暂存和传输消息
  • NameServer :管理Broker
  • Topic:区分消息的种类,一个发送者可以发送消息给一个或者多个Topic:
  • Message Queue:相当于Topic的分区;用于并行发送和接受消息。

集群搭建方式

集群工作流程

  1. 启动NameServer,NameServer起来后监听端口,等待Broker,Producer,Consumer练上来,相当于一个路由控制中心。
  2. Broker启动,跟所有的NameServer保持长连接,定时的去发送心跳检测,注册成功后,NameServer集群就有了Topic跟Broker的映射关系。
  3. 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
  4. Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前Topic存放在哪些的Broker上,轮询从队列列表上选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
  5. Consumer与Producer类似。

消费者的源码解析

消费者获取消息的方式有两种,一种时主动从broker中pull消息,另一种broker主动push给消费者,其实broker主动push消息给消费者,说到底还是消费者先发送请求给broker,表明自己需要哪些topic的消息,然后broker定期扫描,然后收到该topic的消息再发送给消费者。

groupName:创建消费者对象的时候,一般都会指定消费者组名,这个组名groupName也就是订阅组,一般情况下,假如有多个消费者,每一个消费者都使用相同的groupName,那么这些消费者构成的就属于一个组也就是订阅组。Consumer的groupName用于把多个Consumer组织到一起,形成一个订阅组,这样订阅组里的每个消费者都会收到Topic里面的消息

创建pushConsumer

// 实例化DefaultMQPushConsumer ,参数为groupName  消费组组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(topic);
// 设置nameServer的地址
consumer.setNamesrvAddr(nameServer);
// 从哪个位置上接受,此设置只对消费者的第一次启动的消费者有效
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 设置消息
consumer.setMessageModel(MessageModel.CLUSTERING);

setConsumeFromWhere具有的属性

CONSUME_FROM_LAST_OFFSET 第一次启动从队列的最后的位置消费,后续接着上次消费的进度进行消费
CONSUME_FROM_FIRST_OFFSET 第一次启动从队列的开始位置消费,后续接着上次消费的进度进行消费
CONSUME_FROM_TIMESTAMP 第一次启动从指定时间点位置消费,后续再启动接着上次消费的进度开始消费

setMessageModel具有的属性

RocketMQ 支持两种消息模式: Clustering 和Broadcasting 。

BROADCASTING("BROADCASTING") 同一个ConsumerGroup(GroupName相同)里的每一个Consumer都能订阅到所订阅Topic的全部消息,也就是一个消息会被多次分发,被多个消费者消费。
CLUSTERING("CLUSTERING"); 同一个ConsumerGroup(GroupName相同)里的每个Consumer只消费所订阅消息的一部分内容,同一个ConsumerGroup里所有的Consumer 消费的内容合起来才是所订阅Topic 内容的整体,从而达到负载均衡的目的。
consumer.setMaxReconsumeTimes(3);   // 设置最大重复消费的次数

MaxReconsumeTimes可以设置最大的重复消费的次数,如果超过了最大的消费次数,则该消息将被删除,默认为-1,为队列等待。

consumerThreadMin和consumerThreadMax属性

 /**
     * Minimum consumer thread number
     */
    private int consumeThreadMin = 20;

    /**
     * Max consumer thread number
     */
    private int consumeThreadMax = 20;

消费者使用一个ThreadPoolExecutor来处理内部的消费,因此可以设置这两个属性进行更改,提高处理的消费者服务器的处理进程数。

pullBatchSize和consumeMessageBatchMaxSize属性

pullBatchSize是consumer每次从broker拉取的消息最大数,拉取后新消息先存储到consumer内存中。而consumeMessageBatchMaxSize参数代表了,每次将内存中存储的新消息(pullBatchSize参数拉取的消息)传递给开发者自定义的处理方法的最多条数,也是为了提高消费速度的配置项。

    /**
     * Batch pull size
     * defalut size of 32
     */
    private int pullBatchSize = 32;
    /**
     * Batch consumption size
     */
    private int consumeMessageBatchMaxSize = 1;

设置完属性之后,就要对消费者进行订阅

// 消费者开始订阅
consumer.subscribe(topic, tags);

并且需要注册消息监听器

consumer.registerMessageListener(new MessageListenerConcurrently() {
            @SneakyThrows
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                log.info("消费者开始消费消息,消息长度: " + msgs.size());
                System.out.println(msgs.size());
                for (int i = 0; i < msgs.size(); i++) {
                    MessageExt msg = msgs.get(i);
                    log.info("topic -> {},  tags -> {},  msg -> {}" + msg.getTopic() + "->" + msg.getTags() + "->" + new String(msg.getBody()));
                    try {
                        //此处对获取到的消息进行处理

                        //saveData(topic, tags, new String(msg.getBody(), "utf-8"));
                    } catch (Exception e) {
                        e.printStackTrace();
                        if (msgs.get(i).getReconsumeTimes() >= 2) {
                            log.info("失败消息:" + new String(msgs.get(i).getBody(), "utf-8"));
                            //这里写失败消息处理机制,或者不处理

                            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                        } else {
                            log.info("消息消费失败,尝试重试");
                            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                        }
                    }
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

pullMessageService

    private final LinkedBlockingQueue<PullRequest> pullRequestQueue = new LinkedBlockingQueue<PullRequest>();

 @Override
    public void run() {
        log.info(this.getServiceName() + " service started");

        while (!this.isStopped()) {
            try {
                PullRequest pullRequest = this.pullRequestQueue.take();
                this.pullMessage(pullRequest);
            } catch (InterruptedException ignored) {
            } catch (Exception e) {
                log.error("Pull Message Service Run Method exception", e);
            }
        }

        log.info(this.getServiceName() + " service end");
    }

image-20201111114002436

-----待补充

事务消息

image-20201112140150555

大致分为两个流程:正常事务消息的发送及提交和事务消息的补偿流程。

  • 事务消息发送及提交
  1. 发送消息(half消息)。
  2. 服务段响应消息写入结果。
  3. 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
  4. 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
  • 事务补偿
  1. 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次回查
  2. Producer收到回查消息,检查消息对应的本地事务的状态。
  3. 根据本地事务的状态,重新commit或者Rollback

其中补偿截断用于解决消息Commit或者Rollback发生超时或者失败的情况。

  • 事务消息状态

事务消息共有三种状态,提交状态,回滚状态,中间状态:

  1. TransactionStatus.CommitTransaction:提交状态,它允许消费者消费此消息。
  2. TransactionStatus.RollbackTransaction:回滚事务,它代码该消息将被删除,不允许被消费
  3. TransactionStatus.Unknown:中间状态,它代表需要检查消息队列来确定状态。

使用限制

  1. 事务消息不支持延时消息和批量消息
  2. 为了避免单个消息被检查太多次二导致半队列消息累计,我们默认将单个消息检查次数限制为15次,可以通过Broker配置文件的transactionCheckMax参数来修改此限制。
  3. 事务性消息可能不止一次被检查或消费
  4. 建议使用同步的双重写入机制
  5. 事务允许反向查询
posted @ 2020-11-14 16:14  且I听  阅读(104)  评论(0)    收藏  举报