消息隊列原理學習:kafka、RabbitMQ

1、消息隊列

      MQ全稱爲Message Queue消息隊列,生產者不斷的往消息隊列中不斷寫入消息,消費者則可以訂閱隊列中的消息,MQ是遵循了AMQP的具體實現。

     AMQP,即Advanced Message Queuing Protocol,高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。組件之間的解耦,消息的發送者無需知道消息使用者的存在,基於此協議的客戶端與消息中間件可傳遞消息。

消息隊列應用場景

  • 異步處理、應用解耦:
  • 流量削峯:服務器在接收到用戶請求後,首先寫入消息隊列。這時如果消息隊列中消息數量超過最大數量,則直接拒絕用戶請求或返回跳轉到錯誤頁面(服務器熔斷限流)
  • 秒殺業務(緩存)根據秒殺規則讀取消息隊列中的請求信息,進行後續處理

2、RabbitMQ

RabbitMQ是MQ產品的典型代表。RabbitMQ 最初起源於金融系統,是一個由erlang開發的基於AMQP協議的開源實現。用於在分佈式系統中存儲轉發消息,是當前最主流的消息中間件之一。

 

基本概念

1)producer指的是消息生產者,consumer消息的消費者。

2) Queue消息隊列,提供了FIFO的處理機制,具有緩存消息的能力,隊列消息可以設置爲持久化,臨時或者自動刪除,決定數據是否在服務器磁盤上保留。

3) 核心概念Exchange交換機

那麼爲什麼我們需要 Exchange 而不是直接將消息發送至隊列呢?

AMQP 協議中的核心思想就是生產者和消費者的解耦,生產者從不直接將消息發送給隊列。生產者通常不知道是否一個消息會被髮送到隊列中,只是將消息發送到一個交換機。先由 Exchange 來接收,然後Exchange 按照特定的策略轉發到 Queue將各個消息分發到相應的隊列中。在實際應用中我們只需要定義好 Exchange 的路由策略,而生產者則不需要關心消息會發送到哪個 Queue或被哪些Consumer消費。和Queue一樣,Exchange也可設置爲持久化,臨時或者自動刪除。

不同類型的Exchange轉發消息的策略有所區別:

  • Direct直接交換器,Exchange會將消息發送完全匹配的隊列
  • 廣播式交換器,不管消息的ROUTING_KEY設置爲什麼,Exchange都會將消息轉發給所有綁定的Queue。
  • topic主題交換器,工作方式類似於組播,Exchange會將消息轉發和ROUTING_KEY匹配模式相同的所有隊列

4) Binding

所謂綁定就是將一個特定的 Exchange 和一個特定的 Queue 綁定起來。Exchange 和Queue的綁定可以是多對多的關係

5) virtual host

相當於物理的server,可以爲不同應用提供邊界隔離,使得應用安全的運行在不同的vhost實例上,相互之間不會干擾。producer和consumer連接rabbit server需要指定一個vhost。


*如何保證消息100%投遞的方案

  1. 生產端可靠性投遞
  2. 保障消息的成功發出
  3. 保障MQ節點的成功接收
  4. 發送端收到MQ節點(Broker) 確認應答
  5. 完善的消息補償機制

如果想保障消息100%投遞成功,只做到前三步不一定能夠保障。有些極端情況,比如生產端在投遞消息時可能失敗了,或者說生產端投遞了消息,MQ Broker也收到了,MQ Broker在返回確認應答時,由於網絡閃斷導致生產端沒有收到應答,此時這條消息就不知道投遞成功了還是失敗了,所以針對這些情況需要做一些補償機制。

互聯網大廠的解決方案:

  1. 消息落庫,對消息狀態進行打標
  2. 消息的延遲投遞,做二次確認,回調檢查

具體使用哪種要根據業務場景和併發量、數據量大小來決定

方案一:消息信息落庫,對消息狀態進行打標的方案如下圖:

step 1:進行業務數據入庫:比如發送一條訂單消息,首先把業務數據也就是訂單信息進行入庫,然後生成一條消息,把消息也進行入庫,這條消息應該包含消息狀態屬性,並設置初始值比如爲0,表示消息創建成功正在發送中,這種方式缺陷在於我們要對數據庫進行持久化兩次。

step 2:首先要保證第一步消息都存儲成功了,沒有出現任何異常情況,然後生產端再進行消息發送。如果失敗了就進行快速失敗機制。

step 3:MQ把消息收到的結果應答(confirm)給生產端。

step 4:生產端有一個Confirm Listener,去異步的監聽Broker回送的響應,從而判斷消息是否投遞成功,如果成功,去數據庫查詢該消息,並將消息狀態更新爲1,表示消息投遞成功。

假設step 2 已經OK了,在第三步回送響應時,網絡突然出現了閃斷,導致生產端的Listener收不到這條消息的confirm應答,也就是說這條消息的狀態一直爲0了。

step 5:此時我們需要設置一個規則,比如說消息在入庫時候設置一個臨界值timeout,5分鐘之後如果狀態還是0,那就需要把消息抽取出來。這裏,使用分佈式定時任務,去定時抓取DB中距離消息創建時間超過5分鐘的且狀態爲0的消息。

step 6:把抓取出來的消息進行重新投遞(Retry Send),也就是從第二步開始繼續往下走。

step 7:當然有些消息可能由於一些實際的問題無法路由到Broker,比如routingKey設置不對,對應的隊列被誤刪除了,這種消息即使重試多次也仍然無法投遞成功,所以需要對重試次數做限制,比如限制3次,如果投遞次數大於3次,那麼就將消息狀態更新爲2,表示這個消息最終投遞失敗。

 

對於方案一可靠性投遞,在高併發的場景下是否適合?

對於方案一,需要做兩次數據庫的持久化操作,在高併發場景下數據庫將存在性能瓶頸。其實在覈心鏈路中只需要對業務數據進行入庫,消息沒必要先入庫,可以做一個消息的延遲投遞,做二次確認,回調檢查。

方案二:消息的延遲投遞,做二次確認,回調檢查,如下圖:

Upstream Service上游服務也就是生產端,Downstream service下游服務也就是消費端,Callback service是回調服務。

step1:先將業務消息進行入庫,然後生產端將消息發送出去,注意一定是等數據庫操作完成:之後再去發送消息。

step 2:在發送消息之後,緊接着生產端再次發送一條延遲消息投遞檢查,這裏需要設置一個延遲時間,比如5分鐘之後進行投遞。

step 3:消費端去監聽指定隊列,將收到的消息進行處理。

step 4:處理完成之後,發送一個confirm消息,也就是回送響應,但是這裏響應不是正常的ACK,而是重新生成一條消息,投遞到MQ中。

step 5:上面的Callback service是一個單獨的服務,其實它扮演了方案一的存儲消息的DB角色,它通過MQ去監聽下游服務發送的confirm消息,如果Callback service收到confirm消息,那麼就對消息做持久化存儲,即將消息持久化到DB中。

step6:5分鐘之後延遲消息發送到MQ了,然後Callback service還是去監聽延遲消息所對應的隊列,收到Check消息後去檢查DB中是否存在消息,如果存在,則不需要做任何處理,如果不存在或者消費失敗了,那麼Callback service就需要主動發起RPC通信給上游服務,告訴它延遲投遞的這條消息沒有找到,需要重新發送,生產端收到信息後就會重新查詢業務消息然後將消息發送出去。

 

方案二也是互聯網大廠更爲經典和主流的解決方案:

方案二不一定能保障百分百投遞成功,但是基本上可以保障大概99.9%的消息是OK的,有些特別極端的情況只能是人工去做補償了,或者使用定時任務去做。

方案二主要目的是爲了減少數據庫操作,提高併發量。 在高併發場景下,最關心的不是消息100%投遞成功,而是一定要保證性能,保證能抗得住這麼大的併發量。所以能減少數據庫的操作就儘量減少,可以異步的進行補償。

其實在主流程裏面是沒有這個Callback service的,它屬於一個補償的服務,整個核心鏈路就是生產端入庫業務消息,發送消息到MQ,消費端監聽隊列,消費消息。其他的步驟都是一個補償機制

 

KafKa高級消息隊列、數據流處理平臺

Kafka是最初由Linked in公司開發,是一個分佈式、支持分區的(partition、多副本的、多訂閱者,基於zookeeper協調的分佈式日誌系統(也可以當做MQ系統),用scala語言編寫。2010年貢獻給了Apache基金會併成爲頂級開源項目。面向於數據流的生產、轉換、存儲、消費整體的流處理平臺就,Kafka不僅僅是一個消息隊列

Kafka的特性

  • 可以發佈和訂閱且記錄數據的流,類似於消息隊列
  • 數據流存儲的平臺,具備容錯能力,高可用,備份
  • 在數據產生時就可以進行處理,實時數據流處理

Kafka通過Zookeeper管理集羣配置,選舉Leader,以及在Consumer Group發生變化時進行Rebalance。Producer使用push模式將消息發佈到Broker,Consumer使用Pull模式從Broker訂閱並消費消息。

kafka對外使用topic的概念,生產者往topic裏寫消息,消費者讀消息。爲了做到水平擴展,一個topic實際是由多個partition組成的,遇到瓶頸時,可以通過增加partition的數量來進行橫向擴容,單個parition內是保證消息有序

Kafka專用術語:

  • Broker:是Kafka集羣的一個節點,多個broker可以組成一個Kafka集羣。
  • Topic:Kafka以topic的形式來保存不同類別的消息,Kafka集羣能夠同時負責多個topic的分發
  • Partition:topic物理上的分組,一個topic可以分爲多個partition,每個partition都由一系列有序的、不可變的消息組成,這些消息被連續的追加到partition中。offset:partition中的每個消息都有一個連續的序列號叫做offset,用於partition唯一標識一條消息。

Kafka以分區(partition)日誌的形式存儲topic

每個分區都是有序的不可變的消息序列,這些消息序列以追加形式寫到提交日誌上去。每個分區內,每條消息都被分配了一個下標號(offset),這些有序的下標號用以在不同(partition)中唯一確定消息的位置。消息在Kafka上的存儲時間是可配置的,在配置時間範圍內,消息是可以隨時被消費,但是從消息發佈時間開始計算,一旦配置的時間過了,爲了騰出更多的空間,消息將會被丟棄。

Replication:分區的副本

當集羣中有Broker掛掉的情況,系統可以主動地使Replication提供服務。

系統默認設置每一個Topic的Replication係數爲1,所有的讀和寫都從Replication Leader進行,Replication Followers只是作爲備份;Replication Followers必須能夠及時複製Replication Leader的數據增加容錯性與可擴展性

consumer是如何知道自己要消費的消息在那個位置呢

由於每個消息被賦予了在partition中的唯一下標,所以在每個consumer上只需要維護的消息在日誌中的下標位置即可。consumer可以通過控制下標來讀取不同的消息。consumer的這種輕量設計方便了consumer的擴展,某個consumer的去留不會影響集羣。

將日誌分區的目的可以歸納如下:

1. 日誌分區可以避免太大的日誌無法存儲的問題,單個服務器上的容量有限。

2. 這樣可以擁有任意多的分區,從而不會對topic的大小有限制。

3. 日誌分區存儲對後期的並行消費和消息的容錯有很大的幫助。

**topic的分佈式存儲和分佈式的服務請求

  日誌的分區被分佈式存儲到不同的server上,爲了容錯,每個partition可以配置一個冗餘的份數。對於每一個partition,多份冗餘的partition所在的server中只能有一個爲leader,其他的都是follower。在讀寫操作中,都由partition的leader去接受讀寫請求,而其他的follower被動的去複製leader來保證消息的一致性。這樣在以後如果partition的leader的服務如果掛了,這些follower可以被選舉爲leader繼續提供讀寫服務。集羣中的每個server都同時承擔着leader和follower的角色,所以在處理請求的負載上也相對均衡。

producer(消息生產者)

producer將消息發佈到它指定的topic中,並負責決定發佈到topic的哪個分區。

consumer(消息消費者)

  傳統的消息發佈模式有兩種:隊列模式和訂閱發佈模式。隊列模式中,consumer池中的consumer從server從讀取消息,每條消息被一個consumer讀取。訂閱發佈模式中每條消息被廣播到所有的consumer。而Kafka綜合了這兩者,提供了一種consumer group(消費組)的概念。

  有了消費組的概念,每個consumer可以將自己標記爲所屬的組,這樣,Kafka將會將消息傳輸到訂閱組的一個consumer實例,注意,這裏不是將消息傳給訂閱組的所有consumer實例。

  在這種消費組的概念下,如果每個消費組只有一個consumer實例,那和傳統的訂閱發佈模式一樣,如果所有的consumer都在一個組內,則和傳統的隊列模式一樣。更常見的是,每個topic都有若干數量的consumer組,每個組都是一個邏輯上的“訂閱者”,爲了容錯和更好的穩定性,每個組由若干consumer組成。這其實就是一個發佈-訂閱模式,只不過訂閱者是個組而不是單個consumer。消息訂閱組和Kafka集羣的關係如下圖:

  相比傳統的消息系統,Kafka在消息有序性上的保障性更強。傳統的消息系統在有序性和併發性上不能做到很好的互補兼容,傳統的消息系統沒有分區的概念,消息在隊列中有序存儲,但是在多個consumer消費消息時,雖然消息是順序分發的,但是由於消息的異步傳輸,最後並不能保證有序性,然而,如果只讓一個consumer去消費消息,又失去了併發性。但是Kafka通過分區的概念解決了這個難題,在Kafka中,每個分區只可以被分發到一個消費組中的一個consumer,這樣保證了消息消費的有序性,由於一個topic有多個分區,所以併發性上也有保證。注意:在一個消費組中的consumer數量不能超過分區的數量。

Kafka消息系統的幾點保障

1. producer往指定topic的一個partition寫消息時,消息被提交到partition中的順序和producer發送的順序嚴格一致。

2. consumer實例看到的消息的順序和其在partition中存儲的順序一致。

Kafka的高級特性

消息事務的原因:保證數據一致性滿足,不準確的數據處理的容忍度不斷降低

數據傳輸的事務定義

最多一次:消息不會被重複發送,最多被傳輸一次,但也有可能一次不傳輸

最少一次:消息不會被漏發送,最少被傳輸一次,但也有可能被重複傳輸

精確的一次:不會漏傳輸也不會重複傳輸,每個消息都被傳輸一次且僅僅被傳輸一次,這是大家所期望的

 

零拷貝

通過網絡傳輸持久性日誌塊 使用Java Nio實現,底層使用Linux文件系統調用

文件傳輸到網絡的公共數據路徑

第一次拷貝:操作系統將數據從磁盤讀入到內核空間的頁緩存

第二次拷貝:應用程序將數據從內核空間讀入到用戶空間緩存中

第三次拷貝:應用程序將數據寫回到內核空間到socket緩存中

第四次拷貝:操作系統將數據從socket緩衝區複製到網卡緩衝區,以便將數據經網絡發出

零拷貝過程(指內核空間和用戶空間的交互拷貝次數爲零)

第一次拷貝:操作系統將數據從磁盤讀入到內核空間的頁緩存

將數據的位置和長度的信息的描述符增加至內核空間(socket緩存區)

第二次拷貝:操作系統將數據從內核拷貝到網卡緩衝區,以便將數據經網絡發出

 


相關知識:

Zookeeper的角色

領導者(leader),負責進行投票的發起和決議,更新系統狀態

學習者(learner),包括跟隨者(follower)和觀察者(observer)

follower用於接受客戶端請求並想客戶端返回結果,在選主過程中參與投票

Observer可以接受客戶端連接,將寫請求轉發給leader,但observer不參加投票過程,

只同步leader的狀態,observer的目的是爲了擴展系統,提高讀取速度

客戶端(client),請求發起方

      Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫做Zab協議。Zab協議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數Server完成了和leader的狀態同步以後,恢復模式就結束了。狀態同步保證了leader和Server具有相同的系統狀態。

     爲了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務。所有的提議(proposal)都在被提出的時候加上了zxid。實現中zxid是一個64位的數字,它高32位是epoch用來標識 leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch,標識當前屬於那個leader的統治時期。低32位用於遞增計數。

狀態

LOOKING:當前Server不知道leader是誰,正在搜尋

LEADING:當前Server即爲選舉出來的leader

FOLLOWING:leader已經選舉出來,當前Server與之同步

全文檢索ElasticSearch(ES)

ES是一個高度可擴展的、開源的、基於 Lucene 的全文搜索和分析引擎。它允許您快速,近實時地存儲,搜索和分析大量數據,並支持多租戶。通過簡單的 RESTful API 來隱藏 Lucene 的複雜性,從而讓全文搜索變得簡單。

ELK(ElasticSearch, logstash, kibana)技術棧的版本統一配合使用,

Kibana是一個開源分析和可視化平臺,旨在與ES協同工作。您使用Kibana搜索,查看和與存儲在索引中的數據進行交互。您可以輕鬆地執行高級數據分析,並在各種圖表,表格和地圖中可視化您的數據。

 

ES底層原理

Lucene 是一個基於 Java 的全文信息檢索工具包,目前主流的搜索系統Elasticsearch和solr都是基於lucene的索引和搜索能力進行。想要理解搜索系統的實現原理,就需要深入lucene這一層,看看lucene是如何存儲需要檢索的數據,以及如何完成高效的數據檢索。

倒排索引

倒排索引實際上由於應用中需要根據屬性值來查找記錄,這種索引表中的每一項都包含一個屬性值和具有該屬性值的各記錄的地址。

由於不是由記錄來確定屬性值,而是由屬性值來確定記錄的位置,因而稱之爲倒排索引(inverted index),帶有倒排索引的文件稱之爲倒排

關鍵詞代表的是 搜索引擎 給句子分出來的詞,下面的文章代表搜索引擎具體在哪篇文章中搜索出來的數據,我們還可以把這個搜索出的結果更加的細化,變成更加具體的定位 ,第二列文章中代表的含義分別是( 該關鍵詞所在的文章 ,<該關鍵詞在文章出出現的索引位置> ,該關鍵詞出現的次數 )

 

這樣以來搜索引擎就可以根據把句子進行分詞,然後根據對分詞的查詢在文章中的位置將其按照某種方法進行排序操作,完成搜索引擎的排序功能。在搜索引擎中,關鍵詞會被提取出來並且記錄他在文檔中出現的位置和次數,得到正向索引

文檔1->關鍵詞1:出現次數,位置列表->關鍵詞2:出現次數,位置列表

文檔2->關鍵詞1:出現次數,位置列表->關鍵詞2:出現次數,位置列表

當我要去查詢關鍵詞[csgo]要掃描索引庫中所有文檔,找出包含關鍵詞csgo的文檔,再根據打分模型打分,排出名次後給用戶返回,當數據庫文件數量龐大時,這樣掃描全部文檔的辦法肯定無法滿足需求。所以搜索引擎會將正向索引重構爲倒排索把文件到關鍵詞的映射改爲關鍵詞到文件的映射。索引結構變爲:

關鍵詞1->文檔1->文檔2

關鍵詞2->文檔1->文檔2

實現時,將上面三列保存爲詞典文件,頻率文件,位置文件,當搜索一個詞時,先在詞典文件找到該詞,通過詞典文件指針找到位置文件,而詞典文件通常非常小,所以整個過程是毫秒級的。當數據非常龐大達到PB級時,普通的索引無法滿足快速查詢,就會極大地體現出倒排索引的優勢

 

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