細看 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。應用程序可以使用這個字段來指定發送消息的格式名,接收方根據這個名字對消息體做出不同解釋。
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,控制程序退出。