01_RocketMQ

一、简介

1、定义

MQ : 消息队列,是用来保存消息数据的队列
队列 : 数据结构的一种,特征为“先进先出”
消息 : 服务器间的业务请求

2、作用

优点:
应用解耦(异步消息发送)
快速应用变更维护
流量削峰

缺点:
系统可用性降低
系统复杂度提高
异步消息机制
	消息顺序性
	消息丢失
	消息一致性
	消息重复使用

3、产品介绍
在这里插入图片描述
4、结构

生产者
消费者
消息服务器
命名服务器
消息:
	主题
	标签

在这里插入图片描述

5、搭建

官网安装
http://rocketmq.apache.org/docs/quick-start/

docker安装
mkdir -p
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/namesrv/logs 
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/namesrv/store 
/Users/weixingyu/Applications/devlop_tools/rocketmq/conf 
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/broker/logs 
/Users/weixingyu/Applications/devlop_tools/rocketmq/data/broker/store

配置文件
vi broker.conf

brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = {本地外网 IP}


拉去镜像
docker pull rocketmqinc/rocketmq:4.4.0
docker pull styletang/rocketmq-console-ng

创建nameserver容器
docker run -d -p 9876:9876 -v /root/rocketmq/data/namesrv/logs:/root/logs -v /root/rocketmq/data/namesrv/store:/root/store --name rmqnamesrv -e "MAX_POSSIBLE_HEAP=100000000" rocketmqinc/rocketmq:4.4.0 sh mqnamesrv


创建broker容器
docker run -d -p 10911:10911 -p 10909:10909 -v  /root/rocketmq/data/broker/logs:/root/logs -v  /root/rocketmq/data/broker/store:/root/store -v  /root/rocketmq/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf --name rmqbroker -e "NAMESRV_ADDR=namesrv:9876" -e "MAX_POSSIBLE_HEAP=200000000" rocketmqinc/rocketmq:4.4.0 sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf


创建控制台
docker run -d --name rmqconsole -p 9800:8080 -e "JAVA_OPTS=-Drocketmq.namesrv.addr=物理机ip:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" -t styletang/rocketmq-console-ng

二、消息消费模式

依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
       <!-- <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.10</version>
            <scope>test</scope>
        </dependency>-->

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
        </dependency>

1、one2one(基础发送与基础接收)

public class Producer {
    public static void main(String[] args) throws Exception {
        //创建发送消息的对象
        DefaultMQProducer mqProducer = new DefaultMQProducer("group1");

        //设定发送消息的命名服务器
        mqProducer.setNamesrvAddr("localhost:9876");

        //启动发送的服务
        mqProducer.start();

        Message msg = new Message("topic1","hello rocketmq".getBytes(StandardCharsets.UTF_8));
        SendResult result = mqProducer.send(msg);

        System.out.println(result);
        mqProducer.shutdown();
    }
}

public class Consumer {
    public static void main(String[] args) throws Exception {
        //1\创建消费消息对象
        DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");
        //2、设定接收消息的命名服务器地址
        mqConsumer.setNamesrvAddr("localhost:9876");
        //订阅消息,
        mqConsumer.subscribe("topic1","*");
        //3、设定监听器
        mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(new String(messageExt.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        mqConsumer.start();
        System.out.println("接受消息服务已开启运行");
    }
}

2、one2many(负载均衡与广播模式)


public class Producer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer mqProducer = new DefaultMQProducer("group");

        mqProducer.setNamesrvAddr("127.0.0.1:9876");

        mqProducer.start();

        for (int i = 0; i < 10; i++) {
            Message msg = new Message("topic1",("message"+i).getBytes(StandardCharsets.UTF_8));
            mqProducer.send(msg);
        }

        mqProducer.shutdown();

    }
}


public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");

        mqConsumer.setNamesrvAddr("127.0.0.1:9876");
        mqConsumer.subscribe("topic1","*");
//广播模式
       //mqConsumer.setMessageModel(MessageModel.BROADCASTING);
        //负载均衡
        mqConsumer.setMessageModel(MessageModel.CLUSTERING);
        
        mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        mqConsumer.start();

    }
}

3、many2many()


public class Producer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer mqProducer = new DefaultMQProducer("group1");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();

        for (int i = 0; i < 10; i++) {
            Message msg = new Message("topic1",("消息二:"+"message"+i).getBytes(StandardCharsets.UTF_8));
            SendResult result = mqProducer.send(msg);
            System.out.println("返回结果"+result);
        }

        mqProducer.shutdown();

    }
}



public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");

        consumer.setNamesrvAddr("localhost:9876");

        consumer.subscribe("topic1","*");

        consumer.setMessageModel(MessageModel.CLUSTERING);

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();

        System.out.println("已经消费消息");

    }
}

三、消息类别

同步消息:即时性较强,重要的消息,且必须有回执的消息,例如短信,通知(转账成功);
异步消息:即时性较弱,但需要有回执的消息,例如订单中的某些信息
单向消息:不需要有回执的消息,例如日志消息

1、同步消息



/**
 * 测试消息的种类
 */
public class Producer {
    public static void main(String[] args) throws Exception {

        DefaultMQProducer mqProducer = new DefaultMQProducer("group2");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();

        for (int i = 0; i < 5; i++) {
            Message msg = new Message("topic2",("同步消息:"+"message"+i).getBytes(StandardCharsets.UTF_8));
            SendResult result = mqProducer.send(msg);
            System.out.println("返回结果"+result);
        }

        mqProducer.shutdown();

    }
}


public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");

        consumer.setNamesrvAddr("localhost:9876");

        consumer.subscribe("topic2","*");

        consumer.setMessageModel(MessageModel.CLUSTERING);

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();

        System.out.println("已经消费消息");

    }
}

2、异步消息

/**
 * 异步消息
 */
public class Producer {
    public static void main(String[] args) throws Exception {

        DefaultMQProducer mqProducer = new DefaultMQProducer("group2");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();

        for (int i = 0; i < 5; i++) {
            Message msg = new Message("topic2",("异步消息:"+"message"+i).getBytes(StandardCharsets.UTF_8));
            mqProducer.send(msg, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println("返回结果"+sendResult);
                }

                @Override
                public void onException(Throwable throwable) {
                    System.out.println(throwable);
                }
            });
        }

        //添加休眠操作,确保异步消息返回后能够输出
        TimeUnit.SECONDS.sleep(10);

        mqProducer.shutdown();

    }
}





public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");

        consumer.setNamesrvAddr("localhost:9876");

        consumer.subscribe("topic2","*");

        consumer.setMessageModel(MessageModel.CLUSTERING);

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();

        System.out.println("已经消费消息");

    }
}

3、单向消息


public class Producer {
    public static void main(String[] args) throws Exception {

        DefaultMQProducer mqProducer = new DefaultMQProducer("group2");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();
        
        for (int i = 0; i < 5; i++) {
            Message msg = new Message("topic2",("单向消息:"+"message"+i).getBytes(StandardCharsets.UTF_8));
            mqProducer.sendOneway(msg);

        }
        
        mqProducer.shutdown();

    }
}



public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group2");

        consumer.setNamesrvAddr("localhost:9876");

        consumer.subscribe("topic2","*");

        consumer.setMessageModel(MessageModel.CLUSTERING);

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();

        System.out.println("已经消费消息");

    }
}

4、延时消息

在这里插入图片描述


/**
 * 延时消息
 */
public class Producer {
    public static void main(String[] args) throws Exception {

        DefaultMQProducer mqProducer = new DefaultMQProducer("group2");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();

        for (int i = 0; i < 5; i++) {
            Message msg = new Message("topic2",("延时消息:"+i).getBytes(StandardCharsets.UTF_8));

            //设置当前消息的延时间隔
            msg.setDelayTimeLevel(3);

            mqProducer.send(msg);
        }

        mqProducer.shutdown();

    }
}

5、批量消息

注意: 规定每次批量发送的消息不得超过4M

总长度 = topic(字符串字节数) +body(字节数组长度)+ 消息的属性(key与value对应字符串字节数)+日志(固定20字节)


/**
 * 批量消息
 */
public class Producer {
    public static void main(String[] args) throws Exception {

        DefaultMQProducer mqProducer = new DefaultMQProducer("group3");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();

        ArrayList<Message> messages = new ArrayList<>();

        Message msg1 = new Message("topic3", ("批量消息:" + 1).getBytes(StandardCharsets.UTF_8));
        Message msg2 = new Message("topic3", ("批量消息:" + 2).getBytes(StandardCharsets.UTF_8));
        Message msg3 = new Message("topic3", ("批量消息:" + 3).getBytes(StandardCharsets.UTF_8));

        messages.add(msg1);
        messages.add(msg2);
        messages.add(msg3);

        mqProducer.send(messages);

        mqProducer.shutdown();

    }
}

6、过滤消息

  • tag过滤消息
public class Producer {
    public static void main(String[] args) throws Exception {

        DefaultMQProducer mqProducer = new DefaultMQProducer("group1");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();


        Message msg = new Message("topic6","tag1", ("tag过滤消息:" + "message").getBytes(StandardCharsets.UTF_8));
        SendResult result = mqProducer.send(msg);
        System.out.println("返回结果" + result);

        mqProducer.shutdown();

    }
}





public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");

        consumer.setNamesrvAddr("localhost:9876");

        consumer.subscribe("topic6","tag1");

        consumer.setMessageModel(MessageModel.CLUSTERING);

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();

        System.out.println("已经消费消息");

    }
}
  • sql过滤消息

配置文件中配置属性过滤器
enablePropertyFilter=true

public class Producer {
    public static void main(String[] args) throws Exception {

        DefaultMQProducer mqProducer = new DefaultMQProducer("group1");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();


        Message msg = new Message("topic6","tag1", ("tag过滤消息:" + "message").getBytes(StandardCharsets.UTF_8));

        //为消息添加属性
        msg.putUserProperty("vip","1");
        msg.putUserProperty("age","20");
        SendResult result = mqProducer.send(msg);

        System.out.println("返回结果" + result);

        mqProducer.shutdown();

    }
}



public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");

        consumer.setNamesrvAddr("localhost:9876");

        consumer.subscribe("topic6","*");

        //使用消息选择器来过滤对应的属性,语法格式为类sql语法
        consumer.subscribe("topic6", MessageSelector.bySql("vip=1"));

        consumer.setMessageModel(MessageModel.CLUSTERING);

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();

        System.out.println("已经消费消息");

    }
}


7、顺序消息

  • 生产者

public class Producer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer mqProducer = new DefaultMQProducer("group1");

        mqProducer.setNamesrvAddr("localhost:9876");

        mqProducer.start();

       //创建需要执行的任务队列
        ArrayList<Order> orders = new ArrayList<>();

        Order order11 = new Order();
        order11.setId(1);
        order11.setMsg("主单-1");
        orders.add(order11);

        Order order12 = new Order();
        order12.setId(1);
        order12.setMsg("子单-2");
        orders.add(order12);

        Order order13 = new Order();
        order13.setId(1);
        order13.setMsg("支付-3");
        orders.add(order13);


        Order order14 = new Order();
        order14.setId(1);
        order14.setMsg("推送-4");
        orders.add(order14);


        Order order21 = new Order();
        order21.setId(2);
        order21.setMsg("主单-1");
        orders.add(order21);

        Order order22 = new Order();
        order22.setId(2);
        order22.setMsg("子单-2");
        orders.add(order22);

        Order order31 = new Order();
        order31.setId(3);
        order31.setMsg("主单-1");
        orders.add(order31);

        Order order32 = new Order();
        order32.setId(3);
        order32.setMsg("子单-2");
        orders.add(order32);

        Order order41 = new Order();
        order41.setId(4);
        order41.setMsg("主单-1");
        orders.add(order41);

        Order order42 = new Order();
        order42.setId(4);
        order42.setMsg("子单-2");
        orders.add(order42);

        //设置消息进入指定的队列
        for (Order order : orders) {

            Message msg = new Message("orderTopic",order.toString().getBytes(StandardCharsets.UTF_8));

            SendResult result = mqProducer.send(msg, new MessageQueueSelector() {
                //设置当前消息发送时使用哪一个消息队列
                @Override
                public MessageQueue select(List<MessageQueue> list, Message message, Object o) {

                    //根据发送的消息不同,选择不同的消息队列

                    //根据id来选择一个消息队列的对象,并返回
                    int mgIndex = (int) order.getId() % list.size();
                    return list.get(mgIndex);
                }
            }, null);

            System.out.println(result);
        }


        mqProducer.shutdown();
    }
}
  • 消费者


public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");

        mqConsumer.setNamesrvAddr("localhost:9876");

        mqConsumer.subscribe("orderTopic","*");
        
 		//使用单线程的模式从消息队列中获取数据,一个线程绑定一个队列
        mqConsumer.registerMessageListener(new MessageListenerOrderly() {
			/**
             *使用MessageListenerOrderly接口后,对消息队列的处理由一个消息队列多个线程服务,
             * 转化为一个消息队列一个线程服务
             */
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
                for (MessageExt messageExt : list) {
                    System.out.println(Thread.currentThread().getName()+"消息:"+new String(messageExt.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        mqConsumer.start();
        System.out.println("消费开始");
    }
}

8、事务消息

事务补偿过程
在这里插入图片描述
在这里插入图片描述
三种状态
提交状态:允许进入队列,此消息与非事务消息无区别
回滚状态:不允许进入队列,此消息等同于未发送
中间状态:完成了half消息的发送,未对MQ进行二次状态确认

事务仅与生产者有关,与消费者无关

-生产者

public class Producer {
    public static void main(String[] args) throws Exception {
        TransactionMQProducer mqProducer = new TransactionMQProducer("group1");

        mqProducer.setNamesrvAddr("localhost:9876");

        //添加本地事务对应的监听
        mqProducer.setTransactionListener(new TransactionListener() {
            //正常事务过程
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object o) {
           		//return LocalTransactionState.ROLLBACK_MESSAGE;
                return LocalTransactionState.UNKNOW;
                //return LocalTransactionState.COMMIT_MESSAGE;
            }
            //事务补偿过程
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });

        mqProducer.start();

        Message msg = new Message("topic1","hello,transaction message".getBytes(StandardCharsets.UTF_8));
        mqProducer.send(msg);

       //事务补偿的过程一定要保证服务器在正常运行中,否则将无法进行正常的事务补偿;
       // mqProducer.shutdown();
    }
}

  • 消费者
public class Consumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");

        mqConsumer.setNamesrvAddr("localhost:9876");

        mqConsumer.subscribe("topic1","*");

        mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt message : list) {
                    System.out.println(new String(message.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        mqConsumer.start();

        System.out.println("接收消息服务已开启运行");
    }
}

四、集群

1、集群分类

	单机:一个broker提供服务(宕机后服务瘫痪)
	集群:多个broker提供服务(单机宕机后消息无法及时被消费)
		 多个master多个slave
		 	master到slave消息同步方式为同步(较异步消息性能略低,消息无延迟)
		 	master到slave消息同步方式为异步

在这里插入图片描述

brokerName/名称相同是一组,brokerId为0的是master,brokerId不为0的是slave

在这里插入图片描述

2、集群模式

  • 多Master多Slave模式(异步)
每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下:

- 优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样;
- 缺点:Master宕机,磁盘损坏情况下会丢失少量消息。
  • 多Master多Slave模式(同步)
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下:

- 优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
- 缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。

3、集群搭建

  • 总体架构
    在这里插入图片描述
  • 集群工作流程
1. 启动NameServer,NameServer起来后监听端口,等待Broker、Producer、Consumer连上来,相当于一个路由控制中心。
2. Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
3. 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic。
4. Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,轮询从队列列表中选择一个队列,然后与队列所在的Broker建立长连接从而向Broker发消息。
5. Consumer跟Producer类似,跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
  • 搭建
vim /etc/hosts:
	# nameserver
	10.211.55.10 rocketmq-nameserver1
	10.211.55.11 rocketmq-nameserver2
	# broker
	10.211.55.10 rocketmq-master1
	10.211.55.10 rocketmq-slave2
	10.211.55.11 rocketmq-master2
	10.211.55.11 rocketmq-slave1

配置完成后, 重启网卡
	systemctl restart network
	# 关闭防火墙
	systemctl stop firewalld.service 
	# 查看防火墙的状态
	firewall-cmd --state 
	# 禁止firewall开机启动
	systemctl disable firewalld.service

环境变量配置
vim /etc/profile

	#set rocketmq
	ROCKETMQ_HOME=/usr/local/rocketmq/rocketmq-all-4.4.0-bin-release
	PATH=$PATH:$ROCKETMQ_HOME/bin
	export ROCKETMQ_HOME PATH
source /etc/profile

broker配置文件
master1

#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=SYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128




slave2
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=1
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=11011
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=SLAVE
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false
#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128


  • 修改启动脚本文件
vi /usr/local/rocketmq/bin/runbroker.sh
需要根据内存大小进行适当的对JVM参数进行调整:

	#===================================================
	# 开发环境配置 JVM Configuration
	JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"

vim /usr/local/rocketmq/bin/runserver.sh
	JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
  • 服务启动
启动NameServe集群
	分别在192.168.25.135192.168.25.138启动NameServer
	cd /usr/local/rocketmq/bin
	nohup sh mqnamesrv &

启动Broker集群

master1:
	cd /usr/local/rocketmq/bin
	nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a.properties &

slave2:
	cd /usr/local/rocketmq/bin
	nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b-s.properties &


master2
	cd /usr/local/rocketmq/bin
	nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-b.properties &

slave1
	cd /usr/local/rocketmq/bin
	nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-2s-sync/broker-a-s.properties &

4、消息持久化和持久化介质

  • 消息存储
    在这里插入图片描述

  • 存储介质
    在这里插入图片描述

5、顺序写和零拷贝

6、消息存储结构

  • store (commitlog consumerQueue index config)
    在这里插入图片描述

7、刷盘机制

  • 同步刷盘
    在这里插入图片描述
  • 异步刷盘
    在这里插入图片描述
  • 总结
    在这里插入图片描述

8、高可用与主从方案

  • 高可用
    在这里插入图片描述

  • 主从复制
    在这里插入图片描述

9、负载均衡

在这里插入图片描述

10、消息重试和死信队列

  • 消息重试

如果消息重试 16 次后仍然失败,消息将不再投递。如果严格按照上述重试时间间隔计算,某条消息在一直消费失败的前提下,将会在接下来的 4 小时 46 分钟之内进行 16 次重试,超过这个时间范围消息将不再重试投递。

注意: 一条消息无论重试多少次,这些重试消息的 Message ID 不会改变。

消息队列 RocketMQ 默认允许每条消息最多重试 16 次,每次重试的间隔时间如下:
在这里插入图片描述

消费失败后,重试配置方式
public class MessageListenerImpl implements MessageListener {
    @Override
    public Action consume(Message message, ConsumeContext context) {
        //处理消息
        doConsumeMessage(message);
        //方式1:返回 Action.ReconsumeLater,消息将重试
        return Action.ReconsumeLater;
        //方式2:返回 null,消息将重试
        return null;
        //方式3:直接抛出异常, 消息将重试
        throw new RuntimeException("Consumer Message exceotion");
    }
}
  • 死信队列

11、消息幂等

  • producer
public class Producer {



    public static void main(String[] args) throws Exception {

        Jedis jedis = new Jedis("localhost",6379);
        jedis.auth("root");


        //创建发送消息的对象
        DefaultMQProducer mqProducer = new DefaultMQProducer("group1");
        //设定发送消息的命名服务器
        mqProducer.setNamesrvAddr("localhost:9876");


        //启动发送的服务
        mqProducer.start();

        Message msg = new Message("topic1","hello rocketmq".getBytes(StandardCharsets.UTF_8));

        String messageID = UUID.randomUUID().toString();
        msg.setKeys(messageID);
        jedis.sadd("rocketmq",messageID);
        jedis.expire("rocketmq",60*60);

        SendResult result = mqProducer.send(msg);

        System.out.println(result);

        jedis.close();
        mqProducer.shutdown();
    }
}

  • consumer
public class Consumer {
    public static void main(String[] args) throws Exception {

        Jedis jedis = new Jedis("localhost",6379);
        jedis.auth("root");
        //1\创建消费消息对象
        DefaultMQPushConsumer mqConsumer = new DefaultMQPushConsumer("group1");
        //2、设定接收消息的命名服务器地址
        mqConsumer.setNamesrvAddr("localhost:9876");
        //订阅消息,
        mqConsumer.subscribe("topic1","*");

        //3、设定监听器
        mqConsumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                try {
                    for (MessageExt messageExt : list) {
                        System.out.println(new String(messageExt.getBody()));

                        String messageID = messageExt.getKeys();
                        System.out.println("messageID"+messageID);
                        Set<String> smembers = jedis.smembers("rocketmq");
                        for (String smember : smembers) {
                            if(messageID.equals(smember)){
                                System.out.println("redis中存在messageID");
                            }
                        }
                        jedis.close();
                    }
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                } catch (Exception e) {
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
            }
        });
        mqConsumer.start();
        System.out.println("接受消息服务已开启运行");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值