前言
MQ,全稱訊息佇列,現在市面上有很多種訊息佇列,像大家耳熟能詳的RabbitMQ,RocketMQ,Kafka等等,接下來為大家詳細的介紹訊息佇列。
使用場景
俗話說的好,多用多錯,不能為了技術而技術,要結合實際的業務場景使用合適的技術。
例如你用了Redis快取,這時候你也許得考慮主從架構。因為主從架構,你可能得考慮主從切換。同時也許你還得考慮叢集模式。這就大大的提高了開發與維護的成本。因此選擇一種合適的技術是十分重要的。
非同步解耦
- 如果是單體應用,在使用者下單時,下訂單,減庫存,物流記錄這三個操作是同步阻塞的。如果將這三步的操作存放到各自的訊息佇列中,然後監聽這三個訊息佇列,那麼可以大大的減少時間。
- 但同時,由於是分散式應用,你可能得考慮分散式事務的必要性了。此外,為了保證MQ的高可用,你可能得去調研市場上叢集模式支援的最好的中介軟體了。
流量削峰
- 在一些秒殺場景,為了防止極高的併發,將資料庫沖垮,除了可以在負載均衡端進行限流的設定,同時也可以將使用者的請求資料存放到訊息佇列中,然後通過訊息佇列中控制處理速度。
- 同時這個處理的程式可以監聽zk的某個節點,在秒殺結束後,修改這個節點的值,程式監聽到這個節點值的變化,將不再處理請求(可以做成攔截器)。這個有時也被用來作為大資料中流處理的緩衝
市面上的訊息佇列
RabbitMQ
介紹
RabbitMQ 試用於邏輯相對複雜,同時對於佇列的效能需求相對不高的場景。
模型
RabbitMQ
傳送訊息時會先傳送到Exchange
(交換機),指定routing key
。Exchange
和佇列繫結時指定binding key
- 只有當
routing key
和binding key
的匹配規則成立時,才會將訊息發往指定的佇列。 - 兩個消費者監聽同一個佇列,一條訊息不會被兩個佇列同時消費。這是和kafka有區別的。
kafka
kafka topic
RabbitMQ中 資料儲存的最小單位是queue
,而在kafka中 的最小單位是partition
,partition
包含在topic
中,見下圖。
直連模型
一個消費組gourp1
監聽一個topic
, c1和c2會分別消費一個分割槽。
如果是一個消費組的話,必須指定預設消費者組名稱
訂閱模型
多個消費組(每個組中只有一個消費者), 監聽一個topic,每個消費者會消費topic中的所有分割槽。kafka 偏移量(offset)
每條訊息在分割槽中都會有一個偏移量。在訊息進入分割槽前,會給新的訊息分配一個唯一的偏移量,保證了每個分割槽中訊息的順序性。
在RabbitMQ
中, 多個消費者監聽一個佇列,由於訊息是非同步傳送的,由於網路等原因,可能Consumer2
先接受到Message2
,這樣會導致訊息的處理順序不會按照訊息存入佇列的順序。當然,我們可以通過一個消費者監聽一個佇列來保證訊息的有序性。
而kafka呢,多個消費者在一個消費者組中,監聽一個topic
。topic
中的不同的分割槽會被不同的消費者消費(一個消費者消費一個分割槽)。當然,我們只能保證每個分割槽的訊息時被順序消費的,不同的分割槽則不能保證了。
kafka實戰
一個分割槽,一個消費者組,一個消費者
/** 一個消費者 一個消費者組 一個分割槽 **/
public void send1() {
kafkaTemplate.send("test1",0,"test1","data1");
System.out.println("生產者傳送訊息");
}
複製程式碼
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test1(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者接受訊息" + record);
}
}
複製程式碼
生產者傳送訊息
消費者接受訊息ConsumerRecord(topic = test1, partition = 0, offset = 0, CreateTime = 1560260495702, serialized key size = 5, serialized value size = 5, headers = RecordHeaders(headers = [], isReadOnly = false), key = test1, value = data1)
複製程式碼
2個分割槽,一個消費者組,一個消費者,消費所有分割槽
## 增加分割槽數
kafka-topics --zookeeper localhost:2181 --alter --topic test1 --partitions 2
## 結果
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded!
複製程式碼
## 生產者
kafkaTemplate.send("test1",0 #分割槽數,"test1","data1");
kafkaTemplate.send("test1",1,"test11","data11");
System.out.println("生產者傳送訊息");
複製程式碼
## 消費者
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test1(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者1接受訊息" + record);
}
}
複製程式碼
消費者1接受訊息ConsumerRecord(topic = test1, partition = 0, offset = 4, CreateTime = 1560261029521, serialized key size = 5, serialized value size = 5, headers = RecordHeaders(headers = [], isReadOnly = false), key = test1, value = data1)
消費者1接受訊息ConsumerRecord(topic = test1, partition = 1, offset = 1, CreateTime = 1560261029521, serialized key size = 6, serialized value size = 6, headers = RecordHeaders(headers = [], isReadOnly = false), key = test11, value = data11)
複製程式碼
一個分割槽,2個消費者組,2個消費者 ,每個消費者都消費一個分割槽
/** 2個消費者 2個消費者組 1個分割槽 **/
public void send3() {
kafkaTemplate.send("test1",0,"test1","data1");
System.out.println("生產者傳送訊息");
}
複製程式碼
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test1(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者1接受訊息" + record);
}
}
@KafkaListener(topics = {"test1"}, groupId = "test2")
public void test2(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者2接受訊息" + record);
}
}
複製程式碼
消費者1接受訊息ConsumerRecord(topic = test1, partition = 0, offset = 7, CreateTime = 1560261183386, serialized key size = 5, serialized value size = 5, headers = RecordHeaders(headers = [], isReadOnly = false), key = test1, value = data1)
消費者2接受訊息ConsumerRecord(topic = test1, partition = 0, offset = 7, CreateTime = 1560261183386, serialized key size = 5, serialized value size = 5, headers = RecordHeaders(headers = [], isReadOnly = false), key = test1, value = data1)
複製程式碼
2個分割槽,2個消費者,1個消費者組(負載均衡)
/** 2個消費者 1個消費者組 2個分割槽 **/
public void send4() {
kafkaTemplate.send("test1",0,"test1","data1");
kafkaTemplate.send("test1",1,"test11","data11");
System.out.println("生產者傳送訊息");
}
複製程式碼
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test1(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者1接受訊息" + record);
}
}
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test2(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者2接受訊息" + record);
}
}
複製程式碼
消費者1接受訊息ConsumerRecord(topic = test1, partition = 1, offset = 3, CreateTime = 1560261444482, serialized key size = 6, serialized value size = 6, headers = RecordHeaders(headers = [], isReadOnly = false), key = test11, value = data11)
消費者2接受訊息ConsumerRecord(topic = test1, partition = 0, offset = 9, CreateTime = 1560261444482, serialized key size = 5, serialized value size = 5, headers = RecordHeaders(headers = [], isReadOnly = false), key = test1, value = data1)
複製程式碼
2個分割槽,3個消費者,1個消費者組(有個消費者未消費分割槽)
/** 2個消費者 1個消費者組 2個分割槽 **/
public void send4() {
kafkaTemplate.send("test1",0,"test1","data1");
kafkaTemplate.send("test1",1,"test11","data11");
System.out.println("生產者傳送訊息");
}
複製程式碼
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test1(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者1接受訊息" + record);
}
}
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test2(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者2接受訊息" + record);
}
}
@KafkaListener(topics = {"test1"}, groupId = "test1")
public void test3(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
System.out.println("消費者3接受訊息" + record);
}
}
複製程式碼
消費者1接受訊息ConsumerRecord(topic = test1, partition = 1, offset = 3, CreateTime = 1560261444482, serialized key size = 6, serialized value size = 6, headers = RecordHeaders(headers = [], isReadOnly = false), key = test11, value = data11)
消費者2接受訊息ConsumerRecord(topic = test1, partition = 0, offset = 9, CreateTime = 1560261444482, serialized key size = 5, serialized value size = 5, headers = RecordHeaders(headers = [], isReadOnly = false), key = test1, value = data1)
複製程式碼