1. 程式人生 > 其它 >訊息佇列(Message Query)的初學習

訊息佇列(Message Query)的初學習

訊息佇列(Message Query)的初學習

  摘要:本篇筆記主要記錄了對於訊息佇列概念的初次學習、訊息佇列的基礎知識

目錄

1.何為訊息?

  對於訊息佇列這個詞,只要是學習過資料結構,就能很容易明白其中的一般含義,那就是佇列,而剩下的一半就是訊息了,想要完全瞭解訊息佇列的概念,首先就要了解訊息的含義。那什麼是訊息呢?

  在計算機中訊息是一種統稱,廣義上的訊息泛指一切有實際含義的資訊傳輸,每一個訊息的傳送都是有著實際作用的,訊息的發出通常意味著返回,或者意味著某個機制的觸發。如我們在微信中給好朋友發出了:在嗎?的訊息,根據訊息內容,我們往往是期待對方給出一個:或者在忙的迴應的,這就是一個訊息,而好朋友的這兩種回覆也是具備告知意義的,因此也是訊息;我們給好朋友發出訊息:上號兒!,這時你的好朋友會開啟王者榮耀,然後回你一句:開局吧!

這裡就是典型的訊息觸發了某些機制,在此上號打王者就是這個被觸發的機制。廣義上的訊息可以是無意義的,只要我們給別人傳送的東西都可以被稱為訊息,在計算機中,訊息往往代表著請求,和我們廣義上的訊息不同,在計算機系統或者作業系統或者前端系統中,訊息必須具備含義,無意義的訊息是不會被設計出來的,這些訊息往往代表資源上或者事務上的請求,某一個單位A向B傳送訊息,一定是為了獲取到什麼,或者是為了幹什麼事情而發出的。比如:CPU的高速緩衝區中由於資源缺乏,而向記憶體傳送的請求,又比如記憶體中的資源缺乏,向磁碟中傳送的請求,他們都對整個系統的正常執行有著重大的意義,並且都是某方面資源的請求。

2.何為佇列?

  儘管是個很基礎的問題,在此我還是說一下這個問題,包括棧,棧和佇列都是操作受限的線性表,二者的邏輯結構都屬於線性結構,物理結構可以是順序結構也可以是鏈式結構

,區分二者以及普通線性表的重點在於:二者的增刪操作遭到了嚴格的限制。其中棧結構的增刪操作只能在整個線性表的一端進行,而佇列結構的增刪操作被限制為:一端只能進行增加操作,另一端只能進行刪除操作。可以進行增加操作的一端被稱為隊尾,進行刪除操作的一端被稱為隊頭。二者均來源於現實生活中實際存在的狀況或者機制。

3.何為訊息佇列?

  我們不著急上來就直接理解訊息佇列,首先研究一下訊息佇列是為何產生的,任何人造事物的誕生都有著人類充分的理由,人類是一種常有理的生物,那麼訊息佇列的產生想必也是理由充分,首先我們來看一個例子:

3.1.老牛下單買了個Switch

  老牛最近想買個Switch玩塞爾達傳說,於是在攢好錢之後便上某電商平臺下單了,下面是他下單之後,這個電商平臺系統內部乾的事情:

  老牛的訂單確認操作,會啟用服務端系統內部的一系列動作,這些動作必須都正常執行完畢,這個購買活動才算是真正的完成了,商家賣的舒心,老牛買的放心,誰也不會少什麼東西,大家和和氣氣,風平浪靜,而保證系統中的所有動作都能完成的這個模組執行模式,如圖所示,它是一個序列的,也就是說一個任務執行完畢後,它執行成功的訊息不會直接返回給使用者,而是先發送給它在功能邏輯上的下一個任務,用這個訊息來觸發下個任務的執行,按照這種模式,在最後一個任務執行完畢後,它的執行成功訊息不會繼續往下發送,而是返回它的上一個任務,而上一個任務接到自己的邏輯後繼任務完成的資訊後,便連帶自己執行成功的訊息,繼續往回傳送,知道這個訊息返回到了第一個任務,此時,系統才能判定整個大任務完成了,此時再返回給使用者執行成功的訊息。簡而言之,老牛在點選下單後,服務端會依次執行那麼多的任務之後,再返回給老牛一個成功資訊。這種任務執行模式我們稱之為序列化執行

  在這個過程當中我們能夠很輕鬆的發現這種執行模式實際上是具備不少優點的,其最大的優點就是:安全,穩定,這些任務在成功執行完一個之後,才會執行下一個,否則就會陷入等待之中,直到自己執行完畢,當你收到返回資訊的時候,系統確實已經按它說的那樣,整個任務完成了。但是這個系統也確實存在著不足,那就是,使用者發出請求的時候,必須等待自己請求的任務執行結束之後才可以收到成功資訊,我們要知道,任務的執行花費時間,任務之間的通訊也是花費時間的,如果一個任務完成需要5毫秒,資訊的傳送需要1毫秒,那整個任務中的子任務數量比較少的時候,整個任務的完成還是耗時比較少的,如該任務只有三個子任務,那麼整體任務的完成也就花費19毫秒,但實際上事情總沒有那麼簡單,當一個任務的子任務數量非常多,這個等待時間就會等比例增長,最要命的是當該系統使用者數量增加的時候,系統要多執行緒併發的完成每個使用者的任務,這就會加重CPU的負擔,簡而言之,伺服器就慢下來了,而以上的任務執行時間,資訊傳送時間還會變得更慢,使用者就需要等更長時間了。

  人數多了,任務多了,伺服器自然會卡,這是人之常情,但是,我們再苦再難,不能讓使用者也跟著我們受罪,簡而言之,序列系統的缺點在於:直接將系統的任務完成時間暴露給了使用者,並讓使用者直接承擔來自於時間上的壓力,使用者發出請求和獲取迴應的間隔可能會隨著實際情況(如使用者越來越多)而變化,而這變化就會影響到使用者的心情,做買賣的如果激怒客戶無異於自毀長城。因此,儘管訊息佇列的安全性,穩定性更好,因為其模組間耦合度過高,導致的系統吞吐量過低,當系統的應用場景中可能存在使用者多,高併發的時候,就會變得不再適用,這時人們想到了一種更優的解決方案,那就是訊息佇列

3.2.訊息佇列

  我們仍然使用上文提到的老牛買Switch的例子,但是這時我們更換了新的服務端架構,我們使用了一種新的任務模式,如圖所示:

  在這種任務模式中,我們新建了一個訊息佇列,當用戶向服務端發出請求的時候,並不會直接向相關功能模組發出了,而是向訊息佇列中傳送,在訊息佇列獲取到這個請求之後,做的事情是立刻在自己內部按照請求建立一個相應的訊息,並加入佇列,在這個事情完成之後,它便會立即想使用者發出成功操作的資訊,儘管此時使用者的請求還沒有被相關模組執行;與此同時,任務模組也不直接和客戶端對接了,他們直接和訊息佇列對接,它們和訊息佇列對接的關係我們稱之為訂閱,簡而言之,他們會時刻檢測並請求訊息佇列中的訊息,一旦來了新訊息之後,就會按照訊息的要求執行自身任務,並且此時此刻,這些任務功能模組的程式碼也被進行了改寫,他們變得更加內聚了,他們之間的相互聯絡大大減弱,各自執行各自的,不會影響到其他任務的執行。

  在這個系統中,我們對於使用者和功能模組也有了新的稱謂,使用者被稱為訊息的生產者,而功能模組被稱為訊息的消費者,使用者在生產訊息之後,便會直接得到自己成功的返回資訊。在具備了訊息佇列的系統中,系統的吞吐量更大了,使用者收到迴應的速度也更快了,體驗更加好了,但是這種系統也具備不少缺點,由於高內聚導致的模組之間相關性減弱,我們不得不引入事務的概念,我們必須要讓一些任務變成事務,讓他們具備原子性,在這裡我們要尤為注重分散式下的事務安全,我們要保證使用者發出一個訊息之後,相對應的消費者們要真真切切的真正消費了訊息,因為在這裡使用者是不知道自己的請求有沒有被真正消費的,我們在此必須引入事務概念並保證每一個訊息對應的事務安全,也就是說必須保證使用者的請求真的註定完成,他的請求被完成的概率必須是100%,一旦某個訊息對應的消費者們中的其中一個消費者沒有成功消費這個訊息,那麼這個訊息就不能出佇列,並且其他已經成功消費的消費者們必須進行回滾操作,吐出已經吃下去的訊息,讓這個訊息的狀態回到完全沒有被執行的狀態之前,並繼續存在於佇列之中,在之後進行重新的消費。如果在這裡我們不保證事務安全,就等著被使用者投訴吧。與此同時,對於某些事務,內部功能執行的順序可能是確定的,因此對於這種事務,我們必須進行某種設定,保證其內部的順序安全。

  模組耦合度降低並不是完完全全的好事,這會讓模組之間的關係變得更加複雜,因為一個事務可以被多種不同的模組組合來實現,因此某個事務如果出現了錯誤,我們就需要在它對應的模組之間找來找去,如果它的模組很多,並且程式碼不位於同一位置的話,那麼其錯誤進行排查起來就可能比較麻煩,也就是說,由於系統的架構非常複雜,並且需要考慮事務問題,一些錯誤的排查以及修改就會變得更加複雜,更需要動腦子。因此我們可以知道,訊息佇列也不是完全完美的架構模式。

  注意:在以上例子中,訊息佇列只是一個通訊工具,它的主要作用實際上是通訊,解耦和非同步只是它的其他特性,訊息佇列確實用於非同步操作,但是其本來目的還是為了通訊,很多地方都會使用到訊息佇列的通訊思想,如程序通訊。同時上邊的例子還是用到了工廠模式的思想,也就是使用者將自己的訊息託管,然後讓服務端的訊息佇列託管資訊,然後讓後臺的模組們按照佇列來消費資訊,關於這個功能,實際上是有些工廠模式的思想在裡頭的,但是需要注意的是這裡並不是真正的工廠模式,僅僅是有一個託管的思想,因為真正的工廠模式強調的是生產,而這個例子中並沒有生產什麼,只是一些資訊的傳遞,因此不能說是工廠模式,只能說是存在一些託管的思想,且這些託管的思想和訊息佇列並不相關,訊息佇列是一種資訊傳遞的工具,它主要是記性資訊傳遞的,在這裡我們主要研究的,實際上就是這個佇列部分,而因為佇列而產生的的機制和其本身無關,導致整個程式速遞提升快的原因,包括訊息佇列,同時也包括由於訊息佇列而引入的一系列思想,我們可以認為,訊息佇列本事實際上和這些思想是打包的,使用訊息佇列必然引用這些思想,但是訊息佇列的本體實際上就是那個佇列

3.3.序列任務模式與訊息佇列的優缺點對比

一.序列模式

  優點:1.事務上安全穩定,不需要考慮事務完全問題;2.系統架構簡單,錯誤排查起來比較方便。

  缺點:1.模組間耦合度高,容易牽一髮而動全身,一個模組會影響整個程式的執行;2.系統吞吐量低,執行時間直接暴露給使用者,在使用者過多的時候會讓使用者明顯感受到軟體卡慢。

二.訊息佇列

  優點:1.模組間耦合度低,單個模組的錯誤執行不會直接影響到整個事務中其他的模組;2.面向使用者,遮蔽了底層任務的執行情況,沒有直接將真實執行時間暴露給使用者,而是讓訊息佇列託管了使用者的請求,這會讓使用者的體驗更好。

  缺點:1.系統架構複雜,程式碼量大,且功能實現更復雜,問題排查也更復雜;2.由於模組之間的耦合度很低,我們必須考慮到事務安全,在很多情況下,我們必須要制定一套分散式事務解決方案,因為使用訊息佇列的系統,往往是分散式的。

  由此可見二者各有優劣,針對的情況也不同,因為序列模式結構簡單,但是人一多,可能就會卡慢,因此在面向使用者更少的情況下,我們優先考慮使用序列模式完成專案;而訊息佇列在面對高併發,多使用者的情況下更有餘力,因此我們在面向使用者眾多的專案時,優先考慮訊息佇列的模式,然而這時由於高併發需求,我們使用分散式部署是必然的,因此不得不耗費更多的腦細胞來制定一套分散式事務解決方案

3.4.我對訊息佇列本質的理解

  訊息佇列本質上是結構+思想,其物理上的實現時基於佇列這種結構,同時內部節點存放的是訊息,而它的方法論中引入了消費者和生產者這樣的思想,明確了訊息源和訊息接受者之間的關係,同時訊息佇列更偏重於思想性,它是一種通訊的方法,而非某種通訊協議,它並不關係通訊具體是怎麼實現的,它關注的是通訊的更上一層的,實體之間的通訊方式,並且它關注的是通訊,儘管可以實現解耦與非同步,但這些不是它的本來目的,訊息佇列思想並非新思想,它在作業系統的程序通訊中就有所體現,只不過後來被用於了一些更上層的程式設計。

4.訊息佇列的兩種流派

  訊息佇列中主要分為兩個流派,而每個流派之間還有其他分支,接下來就是關於這個的學習。

4.1.有broke的訊息佇列

  broke可以理解為一箇中轉站,在有broke的訊息佇列中,存在很多子佇列以及broke,如下圖:

  在有broke的訊息佇列中,broke是一箇中轉站,生產者直接和它打交道,而非直接和佇列打交道,生產者直接將生產出來的訊息推送給broke就好了,而至於訊息何去何從,需要由broke決定來推送給哪個消費者,或者由消費者自己請求,總之這裡的訊息不會直接進入佇列,需要broke或者消費者決定訊息去哪。broke在主動推送訊息時,是根據某種分類規則以及實際情況進行訊息推送的,對於不同的訊息佇列,可能會推送不同型別的訊息;同時需要注意的是,訊息佇列中還有可能存在一切沒有被訂閱的佇列,broke這時就會根據實際情況,不在這些沒有被訂閱的佇列中放訊息。另外還有一種情況是需要消費者請求訊息,有些訊息broke不會主動推送,訊息在進入佇列系統中不會立即進入某個佇列,而是會在broke中駐留,當某個消費者請求這個訊息的時候,broke才會從自身內部取出這個訊息放到相應的訊息佇列中去。

  在上文中提到了佇列和消費者可以是多對多的,一個消費者可以訂閱多個佇列,而一個佇列也可以被多個消費者訂閱,這就會產生一些問題,如:一個消費者訂閱了兩個佇列,一個訊息來了之後,這兩個佇列可能都會獲取到這個訊息,因此這個消費者就能收到兩次一樣的訊息,就會執行兩次相同的功能程式碼,如果是個訂單服務,就會下兩個訂單,這就出現問題了。因此在這裡,我們需要使用到分散式鎖,我們為了解決重複通知的問題,可以使用分散式鎖。

  在此再次說明一下服務與佇列的關係,服務也就是消費者訂閱了佇列之後,就會一直監聽佇列的狀況,佇列中一旦有了訊息,消費者們就會感知到,並開始消費訊息,並執行相應程式碼。

  在有broke的訊息佇列中,又分為兩種訊息佇列:重topic和輕topic。

4.1.1.重topic

  重topic的有broke的訊息佇列中,不允許訊息在broke中駐留,一旦有訊息進入,必須由broke轉發至相應的佇列,也就是說在重topic中,broke將不再起到訊息倉庫的左右,而僅起到一個訊息中轉的作用,訊息必須存在於佇列中,而不能存在於其他地方。重topic的訊息佇列使用比較多的有如下兩種:Kafka與Rocketmq。

 4.1.1.1.Kafka

  全球訊息處理效能最快的一款訊息佇列,目前支援併發量最大的一種訊息佇列。

 4.1.1.2.Rocketmq

  阿里內部的一位大神根據Kafka的執行原理手寫的,效能和Kafka接近,比Kafka稍微差一點,但是功能上比Kafka多,如順序消費。

4.1.2.輕topic

  輕topic中訊息可以不被放到佇列中,訊息進入訊息佇列系統後,可以不被放入佇列之中,隨便在一個位置呆著等消費者請求就行。輕topic的訊息佇列中有Rabbitmq,它可以沒有主題,可以沒有佇列的概念,訊息來了之後可以不放在佇列中,儘管如此,輕topic的擴充套件性很高,它可以被我們配置成重topic的訊息佇列,它的具體執行方式可以隨著我們的配置而變化。

4.2.無broke的訊息佇列

  沒有使用broke,直接使用socket進行通訊。典型的軟體為Zeromq。在無broke的訊息佇列中,沒有broke,他們直接使用socket進行通訊,消費者直接通過socket與訊息佇列進行一個長連結,從而進行訊息佇列的行為。