目錄
topic拆分多個不同隊列(rocketmq高吞吐的一個原因):
RocketMQ架構圖
RocketMQ 四種集羣部署方式
- 單個Master節點, 缺點就是如果宕機之後可能整個服務不可用;
- 多個Master節點,分攤存放我們的消息,缺點:沒有Slave節點,主的Master節點宕機之後消息數據可能會丟失的;
- 多個Master和Slave節點,之間數據同步採用異步形式,效率非常高,數據可能短暫產生延遲(毫秒級別的)
- 多個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>