細看 MQ 消息頭(MQMD)的功能算法
MQMD 是每一個消息都帶有的消息頭信息,它由若干字段組成,這些字段都是 MQ 設計人員根據總結的應用需求而設置的。應用程序構建消息時應該對這些字段填入恰當的值,對於沒有填入的字段,MQ會用默認值填充。開發應用程序時,充分理解並利用這些字段是十分必要的,這裏逐一爲你們進行介紹,並針對每一個字段指明它在實際編程中通常會用來實現什麼樣的功能:數據庫
StrucId:消息頭結構名,固定爲"MQMD"四個字符。根據這個字段,咱們就可以在應用程序數據包中識別出MQMD的位置。編程
Version :MQMD 版本號。服務器
Report:消息的報告選項,默認值爲 MQRO_NONE。發送方程序經過設置此字段值以指定在消息傳遞出現意外、消息超時、消息到達、消息遞出事件時是否須要報告消息和報告消息要包含什麼內容。對於須要消息報告的程序,須要對這個字段與下面介紹的 ReplyToQ 和 ReplyToQMgr 字段一塊兒進行設置,以對這些消息事件做出反應。數據結構
Expiry:超時字段,單位是 0.1 秒,默認值是 MQEI_UNLIMITED,表示永不過時。消息放到目標隊列裏之後,若是超過這個指定時間尚未被程序讀走,MQ 系統就會丟棄這個消息。若是這個消息設置了 Report 字段要求超時報告,系統會按照Report字段指定的方式返回一個超時報告。應用中一般要進行必要的消息超時機制設計,好比實現SOA框架下爲保持交易一致性而普遍使用的 Compensation 機制,就能夠用超時處理實現。併發
Feedback:反饋字段,此字段與 Report 字段一塊兒使用以指示報告的性質。框架
Encoding:消息中數值數據(binary integer、packed-decimal integers、floating-point numbers等)的編碼方式,默認值是 MQENC_NATIVE,因平臺而異,此值不適用於 MQMD 結構自己的數字數據。實際應用中,爲減小複雜性,要儘可能少使用二進制的數值數據,這時就沒必要考慮此字段。異步
CodedCharSetId:指定消息使用的字符集編碼的 CCSID,默認值是 MQCCSI_Q_MGR,隨平臺不一樣而不一樣。MQ 在須要轉碼時根據這個字段的值來識別消息內容的編碼方式,在主機上通常使用包含 GBK 字符集的 CCSID1388,對應的 UNIX 和 WINDOWS 系統下的 CCSID 是 1386。分佈式
Format:給出描述消息體所符合的數據格式名稱,格式名能夠本身定義,默認值是 MQFMT_NONE。應用程序可使用這個字段來指定發送消息的格式名,接收方根據這個名字對消息體作出不一樣解釋。ide
Priority:消息的優先級,最低優先級是0,默認值是MQPRI_PRIORITY_AS_Q_DEF。
Persistence:消息持久性值,默認值爲 MQPER_PERSISTENCE_AS_Q_DEF。若是消息是持久的,全部操做會記入MQ LOG;若是消息不是持久的則不記 LOG,MQ 系統中斷或重啓意味着還沒被處理的消息將丟失。應用程序設計時要對是否使用持久性消息進行深刻的考慮,雖然持久性消息比較可靠,但它的性能比非持久消息有很大的落後,若是可使用應用邏輯來保證數據一致性,儘可能少使用持久性消息。
MsgId :消息標識,它用來區分消息,由 MQ 自動生成,任意兩個消息的 MsgId 都不一樣。程序執行 MQPUT 後能從 MsgId 字段獲得發出消息的 ID。MsgId 在某個 QMGR 裏是惟一的,但理論上兩個 QMGR 可能產生相同的 MsgId,雖然這種狀況實際上極少會出現。編程上要注意,不要把兩個 QMGR 產生的 MsgId 進行比較。
CorrelId:消息相關標識,應用程序可以使用它來將一個消息與另外一個消息相關,或將一個消息與應用程序正在執行的其它工做相關,默認值全爲空。在一般的作法中,發送請求消息的程序記錄下請求消息的 MsgId,服務程序讀到請求消息,拿出它的 MsgId 放到回覆信息的 CorrelId 字段中,發送程序在 MQGET 獲得回覆消息前,先把記錄的 MsgId 填到消息頭的 CorrelId 中,這樣它就能 GET 到那條特定的回覆消息。CorrelId 也能夠用來設計更復雜的消息傳遞/識別機制。
BackoutCount:記載消息被回滾的次數。具體介紹參見有害的消息處理。
ReplyToQ:這是回覆消息隊列的名稱。本字段和下面的 ReplyToQMgr 一塊兒,構成了消息返回目的地信息。一般消息請求程序在發送請求時,就填好這些字段,消息處理程序只簡單地根據要求進行回覆,經過這種方式實現動態的消息回送機制。
ReplyToQMgr:這是回覆消息隊列所在隊列管理器的名稱,其默認值全爲空,表示返回消息時到本地隊列管理器中去找 ReplyToQ。
UserIdentifier:它屬於 MQMD 的 identity context 字段,是發起消息的應用程序的用戶標識。 其默認值爲空。
AccountingToken:它屬於 MQMD 的 identity context 字段,容許應用程序計算由消息引發的工做量的信息。其默認值爲空。
ApplIdentityData:它屬於 MQMD 的 identity context 字段,是由應用程序定義的信息,可用來提供有關消息或其發起方的信息。其默認值爲空。應用的請求和服務端能夠進行協商,規定這個字段的一些專門用途,經過這個字段,來實現必定的自動化處理。
PutApplType:它屬於 MQMD 的 origin context 字段,是放入消息的應用程序類型,標誌在一個消息傳遞串中最近的對消息進行處理的程序的信息。例如 CICS、IMS、BROKER 等。其默認值爲 MQAT_NO_CONTEXT。經過本字段和下面的PutApplName字段,消息接收程序能夠識別某條消息是誰發送過來的,並根據狀況進行特殊的處理。
PutApplName:它屬於 MQMD 的 origin context 字段,是放入消息的應用程序的名稱。其默認值爲空。
GroupId:消息組標識,MQ for z/OS 不支持消息分組。
MsgSeqNumber:組中邏輯消息的順序號。MQ for z/OS 不支持消息分組。
Offset:數據的偏移量,MQ for z/OS 不支持消息分組技術。
MsgFlags:主要是與消息分組相關的一些狀態信息。
OriginalLength:分段消息的原始長度。
MQMD 的這些字段爲咱們應用程序的開發提供了很好的設施,例如當應用程序請求方須要一種方法肯定哪些返回信息是針對哪條請求時,典型的作法有兩種,一種是爲每一個請求動態建立一個臨時隊列,把隊列名填入 ReplyToQ 字段,響應程序根據 ReplyToQ 裏的值肯定每條消息返回到哪一個隊列裏去;另外一種方法是響應方把原始請求的 MsgId 字段拷貝到它所發回的消息的 CorrelId 字段裏去,發送方用 MsgId 搜索返回信息。
又如 MQ 發送消息的消息頭裏包含了所謂的消息上下文(message context)信息,這些字段描述了消息發送者的一些狀況,消息上下文又包括兩部分:身份鑑別上下文(Identity context)和發送者上下文(Origin context)。身份鑑別上下文(Identity context)描述了消息最初是由誰產生的,包括 MQMD 的 UserIdentifier、AccountingToken和ApplIdentityData 字段;發送者上下文(Origin context)描述了把消息放到隊列上的應用程序的狀況,包括 MQMD 的 PutApplType, PutApplName, PutDate, PutTime, ApplOriginData 字段。當應用程序把一個消息進行轉發時,能夠選擇是從新生成這些上下文仍是從原消息裏繼承上下文。一般的作法是最初的消息發送程序由系統根據用戶信息生成全部消息上下文,對消息作修改或者轉發的應用,只新生成發送者上下文(Origin context),而身份鑑別上下文(identity context) 最好傳遞從原始消息獲得的上下文信息,這樣在消息處理中的任意環節,都可以瞭解到最初發動者的用戶信息。
根據實際業務需求,靈活地運用這些字段,能夠方便地實現複雜的系統功能。
消息類型與傳輸控制
企業中經過 MQ 傳輸的消息有多種不一樣的類型,不一樣類型的消息有不一樣的處理方式。一般消息能夠分爲如下幾類:
數據報(datagram)消息
一種典型的異步消息傳遞形式,其中應用程序發送消息但不須要響應。這種消息類型是結構最簡單、效率最高的類型,應用設計時要儘可能可能地使用這種模式,經過 MQ 提供的可靠的消息傳輸確保消息可以獲得處理,對於要求較高的環境,能夠設置錯誤報告機制。
報告消息(report message)
給出另外一個消息相關信息的消息。報告消息可以代表消息已發送、已到達目標、已到期或因爲某種緣由沒法處理。部分報告信息能夠經過適當的設置,由系統在須要的狀況下自動發送,另外一些能夠由應用程序根據特定的狀況發送。獲得報告的程序,一般須要設置發出消息的 Report、ReplyToQ、ReplyToQMgr 字段來設置但願的報告類型、返回隊列名和返回隊列管理器。在應用設計中,把業務需求和報告消息的功能結合起來,可以解決不少種實際需求。
同步的請求/應答消息(request/reply message)
經過 MQ 實現同步的請求/應答機制:一個程序發送請求消息,經過 MQ 傳送給消息處理方,而後到特定隊列上去守聽應答消息。消息處理程序從 MQ 獲取輸入後,進行特定的處理,而後把應答消息寫入返回隊列。請求程序獲得應答後繼續完成後續處理。這是與前兩種方式相比效率較低的一種模式,它是在異步通訊的基礎上實現的同步應用需求。這時請求/應答程序間要經過上述的動態隊列、MsgId、CorrelId 等手段完成消息的識別。這時還須要進行數據完整性的一些考慮,見下面的分析。
應用的交易一致性控制
MQ 事務支持特性
和傳統的交易處理系統同樣,MQ 把應用程序分紅若干工做單元(UOW),每一個工做單元內部對數據作的更新一般是邏輯相關的,必須同時成功或回滾以保持數據完整性。爲此,MQ 提供了專門的 API:MQBEGIN、MQCMIT 和 MQBACK,分別表示工做單元的開始、提交和回滾。
MQ 不但能夠提供隊列數據操做上的一致性,並且經過全局事務協調器(RRS)的協調下,MQ 能夠實現全局的數據一致性,例如一個程序不但處理了 MQ 消息,並且同時處理了 DB2 數據庫數據、CICS 的 VSAM 文件,這些操做能夠經過統一的一個確認或回滾獲得一致。要注意在 CICS 中的 MQ 程序,工做單元的控制將使用 CICS的EXEC CICS SYNCPOINT 或 SYNCPOINT ROLLBACK,而不是 MQI 的命令。
程序經過每一個 MQGET 和 MQPUT 操做數據時,能夠選擇此次操做是放在 UOW 以內仍是UOW 以外,經過對讀、寫時的選項參數進行設置。若是某個操做放在 UOW 以外,它的操做將不能回滾。
對於在工做單元以內的 MQPUT 操做,因爲交易隔離機制,在 COMMIT 以前,其餘程序是看不到這條消息的,這點在編程中須要注意。
有害消息的處理
因爲 MQ 消息處理有事務特性,若是隊列裏某條消息數據結構存在問題,程序處理它時會發生失敗,這條消息會被自動回滾到隊列中,下次當它再被讀出時,又可能發生失敗,這種狀況若是沒有被意識到,可能會引起嚴重的錯誤循環。爲防止這種狀況的發生,MQ 在消息頭 MQMD 裏設計了 BackoutCount 字段,若是某個消息是在同步點控制之下讀取的,而且因爲某種緣由消息被回滾,消息描述符中的 BackoutCount 字段的值將被加1,良好的程序設計須要判斷該數值,若是它大於某個閾值,則須要使用其它手段來處理該消息,好比再也不對數據進行分析直接放到某個問題隊列裏去。在處理該消息的應用中,能夠將其與設定的閾值作比較,這時,閾值會被寫死在程序中,爲了提升其靈活性,還可使用隊列的 BOTHRESH 和 BOQNAME 屬性。這樣,在例外處理中,利用 MQINQ 查詢獲得閾值 BOTHRESH 的大小,若是超出,能夠將消息轉發到 BOQNAME 指定的隊列中,繼而對該隊列進行相應的處理。這種方法大大加強了應用程序的靈活性。
異步通訊下保持數據一致性的設計
在使用 MQ 進行同步通信的程序設計時,會碰到原來可能會作單一 UOW 的應用,在MQ 的異步應用設計下要劃分紅若干個 UOW:發送程序 PUT 到隊列裏是一個 UOW,接收程序 GET 又是一個 UOW,這就涉及到如何在多 UOW 下保證數據總體的一致性的問題。這種需求,通常能夠經過靈活地使用 MQ 提供的消息生命週期功能和應用的衝正邏輯進行配合。在典型的狀況下:
假設請求系統向服務系統發出請求,調用服務系統上的某個數據改動交易,在請求系統作MQPUT 時,首先要設置請求消息的生命週期 T1,而且在消息到期時將消息丟棄,若是在消息發走以前消息過時,它就會在進入通道以前,被 MQ 系統丟棄;若是消息到達目的地以後,在被對方應用程序取走以前消息過時,它也將被 MQ 系統丟棄。請求系統發送請求後,到應答隊列裏去取結果,若是時間 T1 已過,數據還沒有返回,就給用戶顯示"交易失敗,超時"信息。
在服務系統上,應答程序處理完請求,修改數據庫後,返回結果消息,結果消息也設置生命週期T2(T2<T1),與請求消息不一樣的是,咱們設置在消息到期時發送一個超時報告,將結果消息轉發到另一個特定的隊列中,在這個隊列上用觸發器掛一個衝正程序,這個程序對於全部超時而沒有被請求系統的程序讀走的消息,發動衝正,也就是把前臺沒有收到返回的交易都恢復回去。
在這種設計下,若是請求系統發送後,交易數據沒有被應答系統讀走就超時了,請求系統可以正確顯示"交易失敗,超時",這條請求信息會被系統超時拋棄;若是應答系統已經讀取並完成了數據更改,因爲意外緣由應答消息沒有被請求方讀到,請求方顯示"交易失敗,超時"後將不會再讀這條應答消息,它必將過時,過時後被系統複製到衝正隊列,觸發自動衝正,恢復了數據,整個的效果也是交易失敗,數據沒有改動。經過這樣的設計,能夠看到數據一致性可以很好地保持。
應用的交易一致性控制
MQ 事務支持特性
和傳統的交易處理系統同樣,MQ 把應用程序分紅若干工做單元(UOW),每一個工做單元內部對數據作的更新一般是邏輯相關的,必須同時成功或回滾以保持數據完整性。爲此,MQ 提供了專門的 API:MQBEGIN、MQCMIT 和 MQBACK,分別表示工做單元的開始、提交和回滾。
MQ 不但能夠提供隊列數據操做上的一致性,並且經過全局事務協調器(RRS)的協調下,MQ 能夠實現全局的數據一致性,例如一個程序不但處理了 MQ 消息,並且同時處理了 DB2 數據庫數據、CICS 的 VSAM 文件,這些操做能夠經過統一的一個確認或回滾獲得一致。要注意在 CICS 中的 MQ 程序,工做單元的控制將使用 CICS的EXEC CICS SYNCPOINT 或 SYNCPOINT ROLLBACK,而不是 MQI 的命令。
程序經過每一個 MQGET 和 MQPUT 操做數據時,能夠選擇此次操做是放在 UOW 以內仍是UOW 以外,經過對讀、寫時的選項參數進行設置。若是某個操做放在 UOW 以外,它的操做將不能回滾。
對於在工做單元以內的 MQPUT 操做,因爲交易隔離機制,在 COMMIT 以前,其餘程序是看不到這條消息的,這點在編程中須要注意。
有害消息的處理
因爲 MQ 消息處理有事務特性,若是隊列裏某條消息數據結構存在問題,程序處理它時會發生失敗,這條消息會被自動回滾到隊列中,下次當它再被讀出時,又可能發生失敗,這種狀況若是沒有被意識到,可能會引起嚴重的錯誤循環。爲防止這種狀況的發生,MQ 在消息頭 MQMD 裏設計了 BackoutCount 字段,若是某個消息是在同步點控制之下讀取的,而且因爲某種緣由消息被回滾,消息描述符中的 BackoutCount 字段的值將被加1,良好的程序設計須要判斷該數值,若是它大於某個閾值,則須要使用其它手段來處理該消息,好比再也不對數據進行分析直接放到某個問題隊列裏去。在處理該消息的應用中,能夠將其與設定的閾值作比較,這時,閾值會被寫死在程序中,爲了提升其靈活性,還可使用隊列的 BOTHRESH 和 BOQNAME 屬性。這樣,在例外處理中,利用 MQINQ 查詢獲得閾值 BOTHRESH 的大小,若是超出,能夠將消息轉發到 BOQNAME 指定的隊列中,繼而對該隊列進行相應的處理。這種方法大大加強了應用程序的靈活性。
異步通訊下保持數據一致性的設計
在使用 MQ 進行同步通信的程序設計時,會碰到原來可能會作單一 UOW 的應用,在MQ 的異步應用設計下要劃分紅若干個 UOW:發送程序 PUT 到隊列裏是一個 UOW,接收程序 GET 又是一個 UOW,這就涉及到如何在多 UOW 下保證數據總體的一致性的問題。這種需求,通常能夠經過靈活地使用 MQ 提供的消息生命週期功能和應用的衝正邏輯進行配合。在典型的狀況下:
假設請求系統向服務系統發出請求,調用服務系統上的某個數據改動交易,在請求系統作MQPUT 時,首先要設置請求消息的生命週期 T1,而且在消息到期時將消息丟棄,若是在消息發走以前消息過時,它就會在進入通道以前,被 MQ 系統丟棄;若是消息到達目的地以後,在被對方應用程序取走以前消息過時,它也將被 MQ 系統丟棄。請求系統發送請求後,到應答隊列裏去取結果,若是時間 T1 已過,數據還沒有返回,就給用戶顯示"交易失敗,超時"信息。
在服務系統上,應答程序處理完請求,修改數據庫後,返回結果消息,結果消息也設置生命週期T2(T2<T1),與請求消息不一樣的是,咱們設置在消息到期時發送一個超時報告,將結果消息轉發到另一個特定的隊列中,在這個隊列上用觸發器掛一個衝正程序,這個程序對於全部超時而沒有被請求系統的程序讀走的消息,發動衝正,也就是把前臺沒有收到返回的交易都恢復回去。
在這種設計下,若是請求系統發送後,交易數據沒有被應答系統讀走就超時了,請求系統可以正確顯示"交易失敗,超時",這條請求信息會被系統超時拋棄;若是應答系統已經讀取並完成了數據更改,因爲意外緣由應答消息沒有被請求方讀到,請求方顯示"交易失敗,超時"後將不會再讀這條應答消息,它必將過時,過時後被系統複製到衝正隊列,觸發自動衝正,恢復了數據,整個的效果也是交易失敗,數據沒有改動。經過這樣的設計,能夠看到數據一致性可以很好地保持。
不一樣系統平臺間編碼轉換
IBM 主機與分佈式系統的字符編碼方式不一樣,一方面是基於 EBCDIC 的編碼方式,一方面是ASCII 的編碼方式,在企業級應用中常常要考慮編碼轉換的實現方式。MQ 提供豐富的編碼轉換功能,它能夠根據消息數據包中消息頭裏對消息內容編碼格式的描述,和宿主系統 MQ 定義的 CCSID(應用程序中能夠對系統默認的 CCSID 進行覆蓋)自動進行 EBCDIC、ASCII 與 UNICOD 等編碼之間的轉換。MQ 編碼轉換對中文也有豐富的支持,不但能夠支持 GBK2312 的 6000 多漢字,並且能夠支持 GBK 的更大的字庫。一般在中文環境下,主機使用 CCSID1388,對應的分佈式系統 CCSID 是 1386。
MQ 的編碼轉換,實現時有兩種方式,一種是在發送通道上完成的自動轉碼,另外一種是在MQGET 時使用參數指定 MQ 幫助轉碼。經過通道的轉碼,是在定義發送通道時給定 CONVERT(YES) 或 CONVERT(NO) 參數,若是 CONVERT(YES),MQ 會在傳遞消息時,根據兩個 MQ 服務器的 CCSID 進行編碼轉換。在 MQGET 時的轉碼,能夠在 GetMsgOpts 裏設置 MQGMO_CONVERT 參數,這樣執行 MQGET 時,系統會自動根據消息 MQMD 裏的 CodedCharSetId 值和本地 MQ 服務器的 CCSID 進行轉換。實際實施中,後一種方式使用的更多,由於這時應用程序能夠選擇是讀取轉碼後的數據仍是原始數據,若是在通道上已經轉了,接收方就不能看到原始編碼的數據了。
觸發器類型與併發處理
MQ 支持觸發器,觸發器定義在某個隊列上,當符合觸發條件的事件發生時,MQ 會自動運行觸發器程序,觸發器程序能夠是批量程序(須要 MA12 SupportPac 的支持)、CICS 聯機程序或IMS TS 管理的聯機程序。
觸發器類型
爲了平衡消息平均等待時間和處理效率,MQ 提供三種不一樣的觸發方式:EVERY、DEPTH 和FIRST。
EVERY 觸發方式在每一個新消息到達時被知足,會運行一次應用程序。這時消息平均等待時間最小,但在消息大量到達時會啓動大量觸發器進程,每一個進程只處理一個消息,效率較低。
DEPTH 觸發方式在隊列中積累的消息到達必定數量時被知足,會運行一次應用程序。這個進程會處理全部消息直到隊列空,處理效率高但消息平均等待時間比較長。
FIRST 觸發條件在隊列的深度從 0 到 1 時獲得知足,運行一次應用程序,這個進程也要處理全部消息直到隊列空。它是 EVERY 和 DEPTH 之間的一個平衡,即能提供比較高的處理效率,又不會讓一些消息等待太長,於是是一種經常使用方式。
高性能聯機觸發程序設計
在大企業應用設計時,必定要考慮到程序在大交易量時的表現,必定要提供最高的處理效率。若是考慮觸發程序是 CICS 裏的一支聯機交易程序,咱們會發現,若是使用 EVERY 觸發方式,因爲沒法控制消息什麼時間到來,系統會隨着消息到達的多少出現明顯的"震盪",並且當大量數據同時到達時會在 CICS 裏啓動過多的處理程序,若是不加控制甚至會把 CICS 壓垮。所以 EVERY 方式一般不是 CICS 程序的好的觸發方式。而當使用 FIRST 或 DEPTH 方式時,每一時刻被 MQ 調起的觸發程序只有一支,併發度明顯不足,在交易量大時,有可能造成性能瓶頸。在大交易量環境下進行觸發器程序設計時必定要有新的思路。
具體設計的作法能夠有許多,這裏介紹一種比較簡潔的方法:控制程序/消息處理程序方式。控制程序是定義在 MQ 中的觸發程序,它的功能是負責啓動必定數量的消息處理程序並能夠動態調整,在系統中任什麼時候刻只有一個控制程序在運行,它只使用 MQINQ 獲得隊列深度和隊列上消息處理程序數目等信息,它不直接讀取隊列裏的消息數據。消息處理程序是真正的消息處理者,每一個消息處理程序從輸入隊列讀取數據,通過處理後發送到輸出隊列,它不停地逐條處理消息,直到隊列空,消息處理程序的數目能夠由控制程序控制。控制程序要使用一些特定的算法來決定和控制消息處理程序的數目,它能夠經過 MQINQ 命令獲得隊列的深度和有多少個消息處理程序在打開這個隊列的信息,控制程序能夠設置處理程序數目的初始值、增加值和最高值,在第一次啓動時,啓動初始值數目的處理程序,而後控制程序進入休眠(EXEC CICS DELAY),若干秒後控制程序甦醒,再次檢查隊列深度和消息處理程序數目,若是隊列深度仍在增長,代表處理能力不足,這是就啓動更多的消息處理程序,如此往復直到隊列深度是 0,控制程序退出。