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>

 

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