rocketMQ架构(集群)流程,及顺序消费等问题!

目录

 

RocketMQ架构图 

RocketMQ 四种集群部署方式

broker如果配置集群:

RocketMQ的一些重要问题

broker内存和硬盘都满了怎么解决:

broker集群动态扩容:

topic拆分多个不同队列(rocketmq高吞吐的一个原因):

顺序消息的产生背景:

解决消息顺序的核心思想:

保证消息顺序图1:

保证消息顺序图2(高吞吐量):

单版本rocketMQ如果增加吞吐量:

存在顺序问题的代码:

代码解决顺序问题:

顺序消费完整代码:


RocketMQ架构图 

RocketMQ 四种集群部署方式

  1. 单个Master节点, 缺点就是如果宕机之后可能整个服务不可用;
  2. 多个Master节点,分摊存放我们的消息,缺点:没有Slave节点,主的Master节点宕机之后消息数据可能会丢失的;
  3. 多个Master和Slave节点,之间数据同步采用异步形式,效率非常高,数据可能短暂产生延迟(毫秒级别的)
  4. 多个Master和Slave节点,采用同步形式,效率比较低、数据不会产生延迟。

broker如果配置集群:

多个broker(master)如果知道自己是集群:连接相同的nameServer。

如何设置主/备:BrokerId为0表示Master,非0表示Slave

修改broker.conf文件配置集群:

注意:nameServer默认端口为9876,broker默认监听端口10911

#集群名称,可以区分不同集群,不同的业务可以建多个集群

brokerClusterName=mayikt

 

# Broker 的名称, Master 和Slave 通过使用相同的Broker 名称来表明相互关系,以说明某个Slave 是哪个Master 的Slave 。

brokerName=broker-a

 

#  一个Master Barker 可以有多个Slave, 0 表示Master ,大于0 表示不同Slave 的ID 。

brokerId=0

 

#与fileReservedTim巳参数呼应,表明在几点做消息删除动作,默认值04 表示凌晨4 点。

deleteWhen=04

 

#namesrver集群地址

namesrvAddr=192.168.2.190:9876;192.168.2.191:9876

 

autoCreateTopicEnable=true

#topic默认创建的队列数

defaultTopicQueueNums=4

#是否允许Broker自动创建订阅组,建议线下开启,线上关闭,默认【true】

autoCreateSubscriptionGroup=true

#Broker 监听的端口号,如果一台机器上启动了多个Broker , 则要设置不同的端口号(每个端口间隔最少2,否则会报错),避免冲突。

listenPort=10911

brokerIp=192.168.1.1

# 注意brokerIp是内部通讯ip,内网能ping通192.168.1.1就行或者换别的。

配置完成后正常启动broker即可。

rocketMQ目录下执行:

.\bin\mqbroker.cmd -c .\conf\broker.conf -n 127.0.0.1:9876 &

broker集群启动第二个的时候报错: 

broker的listenPort配置 间隔小于2 则会出现上面的bug

RocketMQ的一些重要问题

broker内存和硬盘都满了怎么解决:

 1. broker支持把消息存放到mysql。

 2. broker扩容

broker集群动态扩容:

直接加broker节点就行因为生产者和消费者获取broker地址是动态获取的(从nameServer中获取)。

添加broker接口后,新broker节点会把自身信息注册到nameServer上。

集群下生产者投递消息:key % broker(主)集群数量 = 消息投递到那台具体的broker上。

topic拆分多个不同队列(rocketmq高吞吐的一个原因):

在rocktmq中(概念):topic是队列的集合。

rocketmq单机情况下,创建一个topic,默认会将该topic分成4个队列进行存放。(默认4个);

kafka中分区的概念 = rocketmq中的多队列。

将该topic分成4个队列进行存放图:

 

看完上图的流程后,发现这样就存在了顺序消费的问题!

顺序消息的产生背景:

生产者向同一个主题中投递的消息行为不同,就会产生顺序问题。

例如行为:insert、update、delete,需要先insert才能update。

- 如果消息行为相同就不需要关注顺序问题,如:发送邮件、短信。

解决消息顺序的核心思想:

同kafka解决思想相同:1. 把消息投递到一个topic队列中,由一个消费者进行消费!

                                      1.2 相同的业务逻辑一定要放到同一个队列中,由一个消费者进行消费。

                                       2. topic中的每个队列1:1均等匹配消费者

MQ遵循先进先出原则。

保证消息顺序图1:

保证消息顺序图2(高吞吐量):

单版本rocketMQ如果增加吞吐量:

只需要增加topic的队列总数即可。 每个队列需要1:1的匹配消费者。

存在顺序问题的代码:

// 生产者
@RestController
public class ProducerController {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @RequestMapping("/sendMsg")
    public String sendMsg() {
        OrderEntity orderEntity = new OrderEntity("insert","增加");
        rocketMQTemplate.convertAndSend("mayikt", orderEntity);

        OrderEntity orderEntityb = new OrderEntity("update","修改");
        rocketMQTemplate.convertAndSend("mayikt", orderEntityb);

        OrderEntity orderEntityc = new OrderEntity("delete","删除");
        rocketMQTemplate.convertAndSend("mayikt", orderEntityc);
        return "success";
    }
    
}

// 消费者
@Service
@RocketMQMessageListener(topic = "mayikt", consumerGroup = "mayiktTopic")
public class OrdeConsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt massage) {
        System.out.println("consumer:" + "队列:"+massage.getQueueId() +
                ","+new String(massage.getBody()));       
    }
}

以上代码打印:

可以看到同一组业务消息是没顺序的,是因为生产者把消息均摊投递到了mayikt主题的队列中了。

代码解决顺序问题:

解决:生产者把消息投递到mayikt主题的同一个队列中,然后一个消费者进行消费。

看下图,注意:生产者投递到同一队列,但是消费数据还是错乱,是因为消费者默认为多线程消费队列,所以需要设置消费者线程数为1。

消费者消费队列默认会开20个线程,

消费者消费发生错乱是因为多线程消费的队列。

添加注解:设置消费者为有序消费消费线程数设为1

为了顺序:主题队列数量和消费者数量最好是1:1

顺序消费完整代码:

生产者:

package com.mayikt.producer;

import com.alibaba.fastjson.JSONObject;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author 蚂蚁课堂创始人-余胜军QQ644064779
 * @title: ProducerController
 * @description: 每特教育独创第五期互联网架构课程
 * @date 2020/1/321:19
 */
@RestController
public class ProducerController {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @RequestMapping("/sendMsg")
    public String sendMsg() throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
        Long orderId = System.currentTimeMillis();
        String insertSql = getSqlMsg("insert", orderId);
        String updateSql = getSqlMsg("update", orderId);
        String deleteSql = getSqlMsg("delete", orderId);
        Message insertMsg = new Message("yushengjun-topic", insertSql.getBytes());
        Message updateMsg = new Message("yushengjun-topic", updateSql.getBytes());
        Message deleteMsg = new Message("yushengjun-topic", deleteSql.getBytes());
        DefaultMQProducer producer = rocketMQTemplate.getProducer();

        rocketMQTemplate.getProducer().send(insertMsg
                , new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg,
                                               Object arg) {
                        // 该消息存放到队列0中
                      return  mqs.get(0);
                    }
                }, orderId);
        rocketMQTemplate.getProducer().send(updateMsg
                , new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg,
                                               Object arg) {
                        // 该消息存放到队列0中
                        return  mqs.get(0);
                    }
                }, orderId);
        rocketMQTemplate.getProducer().send(deleteMsg
                , new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg,
                                               Object arg) {
                        // 该消息存放到队列0中
                        return  mqs.get(0);
                    }
                }, orderId);

        return orderId + "";
    }

    public String getSqlMsg(String type, Long orderId) {
        JSONObject dataObject = new JSONObject();
        dataObject.put("type", type);
        dataObject.put("orderId", orderId);
        return dataObject.toJSONString();
    }


}

消费者:

package com.mayikt.consumer;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;

@Service
@RocketMQMessageListener(topic = "yushengjun-topic", consumerGroup = "mayiktTopic", consumeMode
        = ConsumeMode.ORDERLY, consumeThreadMax = 1
)
public class OrdeConsumer implements RocketMQListener<MessageExt> {

    @Override
    public void onMessage(MessageExt message) {
        System.out.println(Thread.currentThread().getName() + "," +
                "队列" + message.getQueueId() + "," + new String(message.getBody()));
    }
}

订单实体类:

package com.mayikt.entity;

import java.util.Date;
import lombok.Data;

@Data
public class OrderEntity {

    private Long id;
    // 订单名称
    private String name;
    // 订单时间
    private Date orderCreatetime;
    // 下单金额
    private Double orderMoney;
    // 订单状态
    private int orderState;
    // 商品id
    private Long commodityId;

    // 订单id
    private String orderId;

    public OrderEntity(Long id, String name, Date orderCreatetime, Double orderMoney, int orderState, Long commodityId, String orderId) {
        this.id = id;
        this.name = name;
        this.orderCreatetime = orderCreatetime;
        this.orderMoney = orderMoney;
        this.orderState = orderState;
        this.commodityId = commodityId;
        this.orderId = orderId;
    }

    public OrderEntity() {

    }
}

运行:

package com.mayikt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class AppRocketMQ {
    public static void main(String[] args) {
        SpringApplication.run(AppRocketMQ.class);
    }
}

yml配置文件:

rocketmq:
  ###连接地址nameServer
  name-server: 192.168.212.169:9876;
  producer:
    group: mayikt_producer

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
server:
  port: 8088

pom: 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mayikt</groupId>
    <artifactId>mayikt_rocketmq</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>
        <!-- springboot-web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!-- mysql 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- 阿里巴巴数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.14</version>
        </dependency>

    </dependencies>

</project>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章