《大型網站系統與JAVA中間件實踐》讀書筆記-消息中間件

消息中間件數據庫

 

1.消息中間件的價值安全

1.1 透過示例看消息中間件對應用的解耦服務器

1.1.1.經過服務調用讓其餘系統感知事件發生的方式網絡

假設咱們要作一個用戶登陸系統,其中須要支持的一個功能是,用戶登陸成功 後發送一條短信到用戶的手機,算是一個用戶安全的選項,如圖6-2所示。異步

而後,咱們須要把用戶登陸的信息(時間、IP、用戶名等數據)傳給咱們的安全 系統,安全系統會進行安全策略相關的處理和判斷。此時的結構會變成圖6-3所示的 樣子。分佈式

這樣看起來還好,可是若是再增長一些登陸成功後須要被調用的系統呢?如圖 6-4所示。post

這會讓登陸系統變得很是複雜。每增長一個在登陸成功後須要被調用的系統, 就須要修改登陸系統來進行相關的調用。優雅一點的實現是把這個登陸成功後的服 務調用變爲一種可擴展的配置,甚至能夠動態生效,但這隻能下降變動時的開發和 部署成本,並無下降複雜性。登陸系統要被迫依賴很是多的系統。優化

1.1.2.經過引入消息中間件解耦服務調用spa

咱們思考一下,若是從登陸系統的角度來看,這些系統是登陸系統必須依賴的 嗎?答案是否認的.登陸系統只須要驗證用戶名和密碼的合法性(也許還會包含驗證碼等登陸驗證合法性的必須要素),因此登陸系統必須依賴的是可以提供用戶名、 密碼的系統,而圖6-4中的系統其實都不是登陸系統必須依賴的系統。相反,這些系統是必須依賴登陸系統的,由於它們都關心登陸是否成功這件事情。code

在這樣的場景中,咱們須要經過消息中間件把上面的結構解耦,上面結構中的服務調用將會被固定格式的消息的傳遞所取代。登陸系統負責向消息中間件發送消 息,而其餘的系統則向消息中間件來訂閱這個消息,而後完成本身的工做,如圖6-5 所示。

經過消息中間件解耦,登陸系統就不用關心到底有多少個系統須要知道登陸成 功這件事了,也不用關心如何通知它們,只須要把登陸成功這件事轉化爲一個消息 發送到消息中間件就能夠了。這樣,須要瞭解登陸成功這件事的系統本身去消息中 間件訂閱就好了。而且各個系統之間也是互不影響的。

這裏須要注意一點,就是當登陸成功時須要向消息中間件發送一個消息,那麼 咱們必須保證這個消息發送到了消息中間件,不然依賴這個消息的系統就沒法工做 了。這個問題有一個不太優雅的解決方式,如圖6-6所示。

圖6-6所示的思路是,咱們在數據庫中記錄狀態,而後讓用到這個狀態的系統本身來查。這個記錄狀態的數據庫是操做中必定會依賴的數據庫,若是它出問題就會致使對狀態的記錄不成功,業務操做也就不會成功了。圖6-6所示是把狀態記錄在用戶庫的用戶記錄上了。不過這個例子是有一個小問題的,就是若是用戶讀信息時數據庫正常,這時就能完成密碼的驗證,可是若是去記錄狀態時數據庫不可用, 那就仍是有問題的(後面咱們會看到如何解決)。若是這裏只是對數據庫的寫操做 的話,那就沒有問題了,例如修改用戶信息的操做,那麼是能夠在一個SQL中完成用戶信息的更改並設置要發送短信這個狀態,這樣能夠保證操做自己和狀態更新的原子性。

對於須要感知狀態的應用來講,須要定時輪詢數據庫以查看狀態,而且在作完操做後,須要更改狀態從而使得下次就不用再處理了。

能夠說這是一個能解決問題的work around方法,實現也比較簡單。不過也存在如下幾個問題:

•增長了業務數據庫的負擔。一個狀態字段所佔的空間還能夠接受,可是這個數據庫須要被其餘系統持續地定時輪詢,而且進行更新,這就大大增長了數據庫的負擔。

•依賴的複雜和不安全。該方案使得發送短信的服務要依賴業務數據庫,這致使依賴複雜而且不合理,另外,發送短信的服務對數據庫記錄有修改的權限, 這也不安全。

•擴展性很差。對於前面的多個須要在業務動做成功後來作後續工做的系統, 若是把該方式用於這樣的系統的話,咱們就須要增長不少個字段,或者使這 些字段變得可共享又相互不能影響。而且會增長大量的定時對業務數據庫的 輪詢請求。

對於這些問題,咱們也指望經過消息中間件來解決。

2.互聯網時代的消息中間件

在開始介紹互聯網時代的消息中間件前,咱們必須講一下JMS.JMSJava Message Service的縮寫,它是Java EE (企業版Java)中的一個關於消息的規範,而HometqActiveMQ等產品是對這個規範的實現。若是是企業內部或者一些小型的系統,直接使 用JMS的實現產品是一個經濟的選擇,而在大型系統中有一些場景不適合使用JMS

在大型互聯網中,咱們採用消息中間件能夠進行應用之間的解耦以及操做的異步,這是消息中間件的兩個最基礎的特色,也正是咱們須要的。在此基礎上,咱們着重思考的是消息的順序保證、擴展性、可靠性、業務操做與消息發送一致性,以 及多集羣訂閱者等方面的問題,這些內容會在後面的小節中呈現給讀者。咱們從上 一節提到的保證消息必定被處理開始介紹。

2.1 如何解決消息發送一致性

2.1.1消息發送一致性的定義

首先,咱們須要弄清楚消息發送一致性到底是什麼。消息發送一致性是指產生消息的業務動做與消息發送的一致,就是說,若是業務操做成功了,那麼由這個操做產生的消息必定要發送出去,不然就丟失消息了。而另外一方面,若是這個業務行爲沒有發生或者失敗,那麼就不該該把消息發出去。

2.1.2消息發送一致性很難保證嗎

若是要寫處理業務邏輯的代碼和發送消息的代碼,該怎麼寫呢?

下面是一段僞代碼,是在某些實踐中的用法。從中能夠看到如下兩個問題。

void fool (){

//業務操做

//例如寫數據庫,調用服務等

//發送消息 }

•業務操做在前,發送消息在後,若是業務失敗了還好(固然業務本身不以爲好),若是成功了,而這時這個應用出問題,那麼消息就發不出去了。

•若是業務成功,應用也沒有掛掉,可是這時消息系統掛掉了,也會致使消息發不出去。 咱們來看另一種作法,僞代碼以下:

void fool () {

//發送消息 //業務操做

//例如寫數據庫,調用服務等 }

這種方式更不可靠,在業務尚未作時消息就發出了。

在具體的工程實踐中,第一種作法丟失消息的比例相對是很低的。固然,對於要求必須保證一致性的場景,上面的兩種方案都不能接受。

2.1.3你們熟知的JMS有辦法嗎

使用JMS能夠實現消息發送一致性嗎?咱們來看看JMS發送消息的部分。首先看看JMS中幾個比較重要的要素。

  • Destination,是指消息所走通道的目標定義,也就是用來定義消息從發送端 出後要走的通道,而不是最終接收方。Destination屬於管理類的對象。
  • ConnectionFactory,從名字就能看出來,是指用於建立鏈接的對象 ConnectionFactory屬於管理類的對象。
  • Connection,鏈接接口,所負責的重要工做是建立Session。
  • Session,會話接口,這是一個很是重要的對象,消息的發送者、接收者以及 消息對象自己,都是由這個會話對象建立的。
  • MessageConsumer,消息的消費者,也就是訂閱消息並處理消息的對象。
  • MessageProducer,消息的生產者,就是用來發送消息的對象。
  • XXXMessage,是指各類類型的消息對象,包括BytesMessage、MapMessage、 ObjectMessage、StreamMessage 和 TextMessage 5 種。

在JMS消息模型中,有Queue和Topic (在後面會詳細介紹)之分,因此,前面的 Destination、ConnectionFactory、Connection、Session、MessageConsumer、 MessageProducer都有對應的子接口。表6-1顯示了前面各要素在Queue模型(PTP Domain)和 Topic 模型(Pub/Sub Domain)下的對應關係。

此外,在JMS的API中,咱們看到不少以XA開頭的接口,它們其實就是支持 XA協議的接口,它們與表6-1中各要素的對應關係如表6-2所示。

能夠看到,XA 系列的接口集中在 ConnectionFactory、Connection 和 Session 上, 而 MessageProducer、QueueSender、TopicPublisher、MessageConsumer、QueueReceiver 和TopicSubscriber則沒有對應的XA對象。這是由於事務的控制是在Session層面上 的,而 Session 是經過 Connection 建立的,Connection 是經過 ConnectionFactory 建立 的,因此,這三個接口須要有XA系列對應的接口的定義。Session、Connection、 ConnectionFactory在Queue模型和Topic模型下對應的各個接口也存在相應的XA系 列的對應接口。

下面展現了消息最重要的要素(消息、發送者、接收者)與幾個基本元素之間 的關係。

ConnectionFactory->Connection->Session->Message Destination + Session-^ MessageProducer Destination + Sessoin-^ MessageConsumer

在JMS中,若是不使用XA系列的接口實現,那麼咱們就沒法直接獲得發送消息給消息中間件及業務操做這兩個事情的事務保證,而JMS中定義的XA系列的接口就是爲了實現分佈式事務的支持(發送消息和業務操做很難作在一個本地事務中, 後面會講到一些變通的作法)。可是這會帶來以下問題。

•引入了分佈式事務,這會帶來一些開銷並增長複雜性。

•對於業務操做有限制,要求業務操做的資源必須支持XA協議,纔可以與發送消息一塊兒來作分佈式事務。這會成爲一個限制,由於並非全部須要與發送消息一塊兒作成分佈式事務的業務操做都支持XA協議。

2.1.4  有其餘的辦法嗎

從上節能夠看到,jMS是能夠解決消息發送一致性的問題的,可是存在一 些限制而且成本相對較高。那麼,咱們有沒有其餘的辦法呢?

咱們來思考一下要解決的問題,咱們但願保證業務操做與發送相關消息的動做是一致的,而前面的簡單方案不能徹底保證,可是出現問題的機率並不大,因此, 咱們但願找到一種解決方案,這種方案對正常流程的影響要儘量小,而在有問題 的場景能解決問題。

從這個方面看,即使能夠作到業務操做都是支持XA的,若是採用這樣的方式引人兩階段提交的話,那麼仍是把方案作得有些重了。

針對這個問題,咱們能夠用圖6-7所示的方案來解決,流程介紹以下。

(1)   業務處理應用首先把消息發給消息中間件,標記消息的狀態爲待處理。

(2)   消息中間件收到消息後,把消息存儲在消息存儲中,並不投遞該消息。

(3)   消息中間件返回消息處理的結果(僅是入庫的結果),結果是成功或者失敗。

(4)   業務方收到消息中間件返回的結果並進行處理:

      a)   若是收到的結果是失敗,那麼就放棄業務處理,結束。

      b)   若是收到的結果是成功,則進行業務自身的操做。

(5)    業務操做完成,把業務操做的結果發送給消息中間件。

(6)    消息中間件收到業務操做結果,根據結果進行處理:

      a)   若是業務失敗,則刪除消息存儲中的消息,結束。

      b)   若是業務成功,則更新消息存儲中的消息狀態爲可發送,而且進行調度,進行消息的投遞。

這就是整個流程。在這裏讀者必定會有一個疑問,即在最簡單的版本中,咱們 只有業務操做和發消息兩步,仍然會可能產生不少異常,那麼如今這個過程的步驟 更多,產生異常的可能點更多,是如何可以保證業務操做和發送消息到消息中間件 是一致的呢?

咱們對每個步驟可能產生的異常狀況來進行分析。

(1)         業務應用發消息給消息中間件。若是這一步失敗了,不管是網絡的緣由還 是消息中間件的緣由,或是業務應用自身的緣由,咱們都會看到業務操做沒有作, 消息也沒有被存儲在消息中間件中,業務操做和消息的狀態是同樣的,沒有問題。

(2)         消息中間件把消息入庫。若是這一步失敗,不管是消息存儲有問題,仍是 消息中間件收到業務消息後有問題,或是網絡問題,可能形成的結果有兩個。一個 是消息中間件失效,那麼業務應用是收不到消息中間件的返回結果的;二是消息中 間件插人消息失敗,而且有能力返回結果給應用,這時消息存儲中都沒有消息。

(3)         業務應用接收消息中間件返回結果異常。這裏出現異常的緣由多是網絡、 消息中間件的問題,也多是業務應用自身的問題。若是業務應用自身沒問題,那 麼業務應用並不知道消息在消息中間件的處理結果,就會按照消息發送失敗來處理, 若是這時消息在消息中間件那裏入庫成功的話,就會形成不一致。若是是業務應用 有問題,那麼若是消息在消息中間件中處理成功的話,也就會形成不一致了;若是 未處理成功,則仍是一致的。

(4)        業務應用進行業務操做。這一步不會產生太大問題。

(5)        業務應用發送業務操做結果給消息中間件。若是這一步出現問題,那麼消 息中間件將不知道該如何處理已經存儲在消息存儲中的消息,可能會形成不一致。

(6)        消息中間件更新消息狀態。若是這一步出現問題,與上一步所形成的結果 是相似的。

從上面的分析能夠看出,須要瞭解的兩個主要的控制狀態和流程的節點就是業務應用和消息中間件,咱們能夠分別從業務應用和消息中間件的視角來梳理一下, 如表6-3和表6-4所示。

 

 

從上面的梳理和分析能夠看到,對於各類異常狀況咱們遇到的狀態有以下三種:

  • 業務操做未進行,消息未入存儲。
  • 業務操做未進行,消息存人存儲,狀態爲待處理。
  • 業務操做成功,消息存人存儲,狀態爲待處理。

這三種狀況中,第一種狀況不須要進行額外的處理,由於自己就是一致的;第 二種和第三種都須要瞭解業務操做的結果,而後來處理已經在消息存儲中、狀態是待處理的消息。

那麼如何瞭解業務操做的結果呢?

圖6-8展現了這個過程。由消息中間件主動詢問業務應用,獲取待處理消息所對應的業務操做的結果,而後業務應用須要對業務操做的結果進行檢查,而且把結果發送給消息中間件(業務處理結果有失敗、成功、等待三種,等待是多出來的一種狀態,表明業務操做還在處理中),而後消息中間件根據這個處理結果,更新消息狀態。能夠說這是發送消息的一個反向的流程。

一樣,這個流程也會出現不少異常。不過這個4步的流程就是爲了確認業務處理操做結果,真正的操做只是根據業務處理結果來更改消息的狀態,因此,前面3 步都與查詢相關,若是失敗就失敗了,而最後一步的更新狀態若是失敗了,那麼就定時重複這個反向流程,重複查詢就能夠了。

發送消息的正向流程和檢查業務操做結果的反向流程合起來,就是解決業務操做與發送消息一致性的方案。在大多數的狀況下,反向流程是不須要工做的。咱們來看看正向流程是否帶來了額外的負擔,對好比表6-5所示。

從上面的對比能夠看到,解決一致性的方案是隻增長了一次網絡操做和一次更新存儲中消息狀態的操做,就是第5步和第6步兩步。而前面4步和傳統方式所作的事情都同樣,只是順序有所不一樣。因此,總體上帶來的額外開銷並不大,並且還 有可優化的點。

接着來看一下使用方式。能夠看到解決一致性的方案中,在業務應用那裏是有一個固化的流程的,能夠提供一個封裝來方便業務應用的使用,僞代碼以下。

Result postMessage(Message, PostMessageCallback){

//發送消息給消息中間件 //獲取返回結果 //若是失敗,返回失敗 //進行業務操做

//獲取業務操做結果 / /發送業務操做結果給消息中間件 //返回處理結果 }

能夠看到,咱們能夠把實現邏輯封裝在一個調用中,而後把業務的操做包裝成 一個對象傳進來,而後整個流程就能夠控制在這個方法中了。固然,除了發送一致性的消息以外,也應該提供一個傳統的發送消息的接口, 也就是不支持發送一致性的發送接口。此外,爲了適應其餘的場景(例如與現有的事務處理流程結合等),也會提供獨立的接口,就會把這個流程的控制權交給業務應用自身。

2.2 如何解決消息中間件與使用者的強依賴問題

回顧一下解決業務操做和發送消息一致性的方案,會發現咱們更多地關注瞭如何保持和解決一致性的問題,可是忽略了一個問題,那就是消息中間件變成了業務應用的必要依賴。也就是說,若是消息中間件系統(包括使用的消息存儲、業務應用到消息中間件的網絡等)出現問題,就會致使業務操做沒法繼續進行,即使當時業務應用和業務操做的資源都是可用的。

咱們須要思考如何解決這個問題,思路有以下三種:

•提供消息中間件系統的可靠性,可是沒有辦法保證百分之百可靠。

•對於消息中間件系統中影響業務操做進行的部分,使其可靠性與業務自身的可靠性相同。

•能夠提供弱依賴的支持,可以較好地保證一致性。

第一種方案,提高消息中間件系統的可靠性是必需要作的事情,可是咱們沒法保證百分之百可靠。

第二種方案,讓消息中間件系統中影響業務操做的部分與業務自身具備一樣的可靠性,其實就是要保證若是業務能操做成功,就須要消息可以入庫成功。由於如 果消息中間件出問題了,能夠接受投遞的延遲,可是須要保證消息入庫,這樣業務操做才能夠繼續進行。那麼,可行的方式只有一種,如圖6-9所示。

咱們把消息中間件所須要的消息表與業務數據表放到同一個業務數據庫中,這 樣,業務應用就能夠把業務操做和寫人消息做爲一個本地事務來完成,而後再通知消息中間件有消息能夠發送,這樣就解決了一致性的問題。從圖6-9中能夠看到這一 步是虛線表示的,表明它不是一個必要的操做和依賴。消息中間件會定時去輪詢業務數據庫,找到須要發送的消息,取出內容後進行發送。這個方案對業務系統有如 下三個影響:

•須要用業務本身的數據庫承載消息數據。

•須要讓消息中間件去訪問業務數據庫。

•須要業務操做的對象是一個數據庫,或者說支持事務的存儲,而且這個存儲必須可以支持消息中間件的需求。

咱們在上面的基礎上進行一下變通,如圖6-10所示。這個方案和圖6-9中方案的區別是,消息中間件再也不直接與業務數據庫打交道。消息表仍是放在業務數據庫 中,徹底由業務數據庫來控制消息的生成、獲取、發送及重試的策略。這樣,消息 中間件就不須要與衆多使用這種消息一致性發送的業務方的數據庫打交道了,不過 比較多的邏輯是從消息中間件的服務端移動到消息中間件的客戶端,而且在業務應 用上執行。消息中間件更多的是管理接收消息的應用,而且當有消息從業務應用發 過來後就只管理投遞,把原來的調度、重投、投遞等邏輯分到了客戶端和服務端 兩邊。

圖6-9和圖6-10中的兩種方式雖然已經解決了大部分問題,可是它們都要求業務操做是支持事務的數據庫操做,具備必定的限制性,這裏咱們能夠再進行一 下變通。

咱們考慮把本地磁盤做爲一個消息存儲,也就是若是消息中間件不可用,又不肯或不能侵入業務本身的數據庫時,能夠把本地磁盤做爲存儲消息的地方,等待消息中間件回覆後,再把消息送到消息中間件中(如圖6-11所示)。全部的投遞、重試等管理,仍然是在消息中間件進行,而本地磁盤的定位只是對業務應用上發送消息 必定成功的一個保證。

這種方式存在的風險是,若是消息中間件不可用,並且寫入本地磁盤的數據也 壞了的話,那麼消息就丟失了。這確實是個問題,因此,從業務數據上進行消息補發纔是最完全的容災的手段,由於這樣才能保證只要業務數據在,就必定能夠有辦法恢復消息了。

將本地磁盤做爲消息存儲的方式有兩種用法,一是做爲一致性發送消息的解決方案的容災手段,也就是說該方式平時不工做,出現問題時才切換到該方式上,二 是直接使用該方式來工做,這樣能夠控制業務操做自己調用發送消息的接口的處理時間,此外也有機會在業務應用與消息中間件之間作一些批處理的工做。

最後,咱們來看一下業務操做與發送消息一致性的方案所帶來的兩個限制。

•須要肯定要發送的消息的內容。由於咱們在業務操做作以前會把狀態標記爲待處理,這要求先能肯定消息內容;這裏能夠有一個變通,即先把主要內容也就是可以標記該次業務操做特色的信息發過來,而後等業務操做結束後須要更新狀態時再補全內容。不過這仍是要求在業務操做以前可以肯定一些索引性質的信息。

•須要實現對業務的檢查。也就是說爲了支持反向流程的工做,業務應用必須可以根據反向流程中發回來的消息內容進行業務操做檢查,確認這個消息所指向的業務操做的狀態是完成、待處理,仍是進行中,不然,待處理狀態的消息就沒法被處理了。

2.3 消息模型對消息接收的影響

前面講述了消息發送端的內容,咱們接下來看一下消息模型。在JMS中,有Queue (點對點)和Topic (發佈/訂閱)兩種模型,咱們來看看這兩種模型的特色。

2.3.1  JMS Queue模型

圖6-12顯示的是JMS Queue模型,能夠看到,應用1和應用2發送消息到JMS 服務器,這些消息根據到達的順序造成一個隊列,應用3和應用4進行消息的消費。 這裏須要注意的是,應用3和應用4收到的消息是不一樣的,也就是說在JMS Queue 的方式下,若是Queue裏面的消息被一個應用處理了,那麼鏈接到JMS Queue上的 另外一個應用是收不到這個消息的,也就是說全部鏈接到這個JMS Queue上的應用共同消費了全部的消息。消息從發送端發送出來時不能肯定最終會被哪一個應用消費, 可是能夠明確的是隻有一個應用會去消費這條消息,因此JMS Queue模型也被稱爲 Peer To Peer (PTP)方式。

2.3.2 JMS Topic模型

圖6-13顯示的是JMS Topic模型。從發送消息的部分和JMS Topic內部的邏輯 來看,JMS Topic和JMS Queue是同樣的,兩者最大的差異在於消息接收的部分,在 Topic模型中,接收消息的應用3和應用4是能夠獨立收到全部到達Topic的消息的。 JMS Topic模型也被稱爲Pub/Sub方式。

2.3.3 JMS中客戶端鏈接的處理和帶來的限制

在使用JMS時,每一個Connection都有一個惟一的Clientld,用於標記鏈接的惟一性,也就是說剛纔對Queue和Topic的介紹中,咱們是默認一個接收應用只用了一個鏈接。如今來看一下多鏈接的狀況,如圖6-14所示。

相關文章
相關標籤/搜索