細(xì)心!用心!耐心!

          吾非文人,乃市井一俗人也,讀百卷書(shū),跨江河千里,故申城一游; 一兩滴辛酸,三四年學(xué)業(yè),五六點(diǎn)粗墨,七八筆買(mǎi)賣(mài),九十道人情。

          BlogJava 聯(lián)系 聚合 管理
            1 Posts :: 196 Stories :: 10 Comments :: 0 Trackbacks

          使用 JMS 技術(shù)作為數(shù)據(jù)復(fù)制的解決方案

           

          本文概述了如何使用 Java 消息傳遞系統(tǒng)(JMS)進(jìn)行大型文件的復(fù)制。Dan Drasin 描
          述了解決 Applied Reasoning 公司客戶(hù)的分布式數(shù)據(jù)問(wèn)題的方案,并提供了基于 JMS
          的解決方案的實(shí)現(xiàn)細(xì)節(jié)。他討論了其中的優(yōu)點(diǎn)、一些潛在缺陷以及將 IBM MQSeries(現(xiàn)
          在稱(chēng)為 WebSphere MQ)成功設(shè)置為 JMS 服務(wù)器的一些實(shí)際指示信息。
          背景
          在思考消息傳遞解決方案時(shí),您可能會(huì)想到一個(gè)通過(guò)遠(yuǎn)程消息調(diào)用機(jī)制來(lái)集成兩個(gè)不同
          應(yīng)用程序的系統(tǒng)。一般來(lái)講,對(duì)于不常通信的分布式實(shí)體以及數(shù)據(jù)傳輸量不是很多這樣
          的情況,常常使用這種耦合。較經(jīng)典的示例是,連接到異構(gòu)后端和入口的同構(gòu)接口,這
          些后端和入口指派進(jìn)行用戶(hù)請(qǐng)求的后端處理,然后為最終用戶(hù)表示而對(duì)那些請(qǐng)求進(jìn)行重
          新格式化。
          消息傳遞方法中的公共線程一直有這樣的假定:雖然消息傳遞解決方案在系統(tǒng)之間提供
          健壯、高度可用的通信,但它基本上效率很低,只用來(lái)作為在無(wú)法避免與外部系統(tǒng)通信
          時(shí)的最后一種手段。在出現(xiàn)遠(yuǎn)程方法調(diào)用(RMC)時(shí)關(guān)于消息傳遞的這種觀點(diǎn)就開(kāi)始流行
          一直到出現(xiàn)了更現(xiàn)代的象 CORBA 和 DCOM 那樣的消息傳遞解決方案,而且,通常所應(yīng)用
          的消息傳遞只局限于解決幾類(lèi)問(wèn)題。
          目標(biāo)
          在過(guò)去的十年中,人們對(duì)分布式系統(tǒng)需求有了更深入的理解。新興技術(shù)(象 Java 和 .
          NET)已經(jīng)包含了代碼分布來(lái)作為它們基本編程模型的一部分。通過(guò)這樣做,這些技術(shù)已
          將高度可用性和容錯(cuò)性融入到消息傳遞中,同時(shí)鼓勵(lì)那些提供解決方案的供應(yīng)商交付一
          些系統(tǒng),這些系統(tǒng)在更廣范圍的問(wèn)題上考慮性能。
          近來(lái)我們公司被要求實(shí)現(xiàn)文件分布和復(fù)制的解決方案,在以前這樣的方案需要集成安全
          的 FTP、數(shù)據(jù)庫(kù)復(fù)制和其它一次性解決方案的定制系統(tǒng)。我們沒(méi)有一味地埋頭按照定制
          開(kāi)發(fā)的道路前進(jìn),而是研究了將最新的消息傳遞解決方案應(yīng)用到這個(gè)問(wèn)題的可能性。我
          們發(fā)現(xiàn) JMS 不僅為信息傳送提供必要的基礎(chǔ)結(jié)構(gòu),而且它還能處理我們客戶(hù)要求的、與
          服務(wù)質(zhì)量、安全性、可靠性和性能有關(guān)的所有基礎(chǔ)結(jié)構(gòu)問(wèn)題。本文描述了我們團(tuán)隊(duì)面臨
          的挑戰(zhàn),以及 JMS(以 MQSeries 的形式)如何讓我們滿(mǎn)足并超越客戶(hù)的要求。
          問(wèn)題
          我們的客戶(hù)面臨一個(gè)重大的分布式數(shù)據(jù)難題,在全國(guó)范圍內(nèi)有許多呼叫中心,在全國(guó)各
          地的呼叫中心里接線員要記錄與客戶(hù)之間的交互。必須快速可靠地在遠(yuǎn)程數(shù)據(jù)中心為這
          些記錄建立索引并存檔。建立索引和存檔的存儲(chǔ)過(guò)程不能影響接線員的系統(tǒng)記錄和存儲(chǔ)
          接線員正在與客戶(hù)交互的信息的能力。該客戶(hù)已經(jīng)有了一個(gè)包含組合起來(lái)的代碼、VPN
          和其它技術(shù)的系統(tǒng)。但是,現(xiàn)有的解決方案遠(yuǎn)遠(yuǎn)達(dá)不到性能和可靠性上的目標(biāo),并且它
          是一種拙劣的技術(shù),難以理解并且維護(hù)費(fèi)用很高。
          在開(kāi)發(fā)替代客戶(hù)原有系統(tǒng)時(shí),我們考慮了 JMS 和多種非 JMS 的解決方案,尤其是那些
          基于 FTP 和安全復(fù)制(SCP)的解決方案。然而,非 JMS 解決方案有兩個(gè)主要缺點(diǎn):
          它們對(duì)于安全性方面的缺陷一籌莫展。(FTP 上的安全性漏洞已經(jīng)人人皆知,并且人們
          對(duì)此已廣泛地作了記載。如果需要這方面的例子,請(qǐng)參閱參考資料。)
          它們提供的基礎(chǔ)結(jié)構(gòu)只適用于實(shí)際的數(shù)據(jù)傳送,而對(duì)于處理可靠性、容錯(cuò)性、安全性、
          平臺(tái)獨(dú)立性以及性能優(yōu)化等問(wèn)題,需要定制開(kāi)發(fā)來(lái)解決。
          我們團(tuán)隊(duì)最后得出結(jié)論,對(duì)于添加這些額外的特性所需的開(kāi)發(fā)工作是讓人望而卻步的,
          因此我們決定選用 JMS 解決方案,它可以擺脫這些問(wèn)題。
          解決方案
          我們開(kāi)發(fā)了一個(gè)基于 JMS 的系統(tǒng),它:
          為已記錄的多媒體文件提供可靠存檔
          支持可擴(kuò)展性,可以使多個(gè)數(shù)據(jù)中心接收文件
          支持對(duì)其它數(shù)據(jù)類(lèi)型進(jìn)行存檔
          我們這里正討論的文件比以前那些涉及消息傳遞解決方案的項(xiàng)目中傳送的數(shù)據(jù)還要大(
          50K - 500K)。我們第一個(gè)任務(wù)是確保數(shù)據(jù)大小不會(huì)影響 JMS 解決方案。通過(guò)測(cè)試系統(tǒng)
          傳遞各種大小的消息有效負(fù)載時(shí)的性能,我們?cè)u(píng)估了包括 IBM MQSeries 在內(nèi)的許多 J
          MS 解決方案。結(jié)果顯示:經(jīng)過(guò)適當(dāng)配置,大小達(dá)到 1 兆的消息不會(huì)對(duì)整個(gè)系統(tǒng)性能產(chǎn)
          生顯著影響。因?yàn)槌WR(shí)認(rèn)為消息傳遞解決方案只適用于定期的、小的有效負(fù)載,所以我
          們的結(jié)果是一個(gè)重大發(fā)現(xiàn)。我們繼續(xù)分析系統(tǒng)的體系結(jié)構(gòu)(圖 1 中概述了此體系結(jié)構(gòu))
          ,它可以提供客戶(hù)需要的安全性、高可用性和可靠性。
          圖 1. 高級(jí)系統(tǒng)體系結(jié)構(gòu)
          現(xiàn)有的基礎(chǔ)結(jié)構(gòu)在每個(gè)客戶(hù)機(jī)上有一個(gè)系統(tǒng),當(dāng)接線員與用戶(hù)之間進(jìn)行交互時(shí),它創(chuàng)建
          多媒體文件,以此作為響應(yīng)。此外,還需對(duì)這些文件進(jìn)行存檔。我們的系統(tǒng)啟動(dòng)一個(gè)進(jìn)
          程(運(yùn)行在每個(gè)機(jī)器上)并在已知目錄中查找這些文件。當(dāng)檢測(cè)到新文件時(shí),進(jìn)程將它
          們打包成 JMS 有效負(fù)載并發(fā)送到其中一個(gè)數(shù)據(jù)中心的 JMS 服務(wù)器以便傳遞。一旦 JMS
          服務(wù)器確認(rèn)收到,則除去發(fā)送方中的這些文件。JMS 服務(wù)器將該數(shù)據(jù)傳送到數(shù)據(jù)中心內(nèi)
          的一個(gè)可用處理程序上,進(jìn)行存檔。
          主要概念
          JMS 是特定于 Java 的消息傳遞和排隊(duì)的實(shí)現(xiàn)。在消息傳遞和排隊(duì)中有兩個(gè)基本思想:

          系統(tǒng)通過(guò)使用不連續(xù)的數(shù)據(jù)包進(jìn)行通信,這些數(shù)據(jù)包都有一個(gè)有效負(fù)載(即要傳送的信
          息)和屬性(即該信息的特征以及它應(yīng)如何通信)。這個(gè)數(shù)據(jù)包稱(chēng)為 消息。
          消息不是被發(fā)送給系統(tǒng),而是被發(fā)送到一個(gè)獨(dú)立的保存區(qū)域??梢愿鶕?jù)您的需要確定保
          存區(qū)域的數(shù)量,通過(guò)唯一的名稱(chēng),可以標(biāo)識(shí)并定位它們。每個(gè)保存區(qū)域都可以接收消息
          ,并且根據(jù)配置的不同,該區(qū)域?qū)⒚總€(gè)消息要么傳遞給所有感興趣的系統(tǒng)(發(fā)布-訂閱)
          ,要么傳遞給第一個(gè)感興趣的系統(tǒng)(點(diǎn)對(duì)點(diǎn))。這個(gè)保存區(qū)域稱(chēng)為 目的地。
          我們構(gòu)建的系統(tǒng)采用點(diǎn)對(duì)點(diǎn)的目的地,在 JMS 中稱(chēng)為隊(duì)列。排隊(duì)是圖 1 中顯示的系統(tǒng)
          設(shè)計(jì)的一個(gè)重要方面。該圖顯示了消息正從 JMS 代理直接傳送到接收方的客戶(hù)機(jī)上,但
          這并不十分準(zhǔn)確。實(shí)際上,消息被傳送到一個(gè)隊(duì)列中,接收方客戶(hù)機(jī)從隊(duì)列中檢索它們
          。稍后我們研究實(shí)現(xiàn)細(xì)節(jié)時(shí),這個(gè)區(qū)別將變得非常重要,因?yàn)樗屜到y(tǒng)并行地處理收到
          的消息。
          跨平臺(tái)和交叉供應(yīng)商
          對(duì)我們客戶(hù)機(jī)來(lái)說(shuō)盡量減少對(duì)某家供應(yīng)商的依賴(lài),這意味著,我們所設(shè)計(jì)的代碼應(yīng)該使
          由于更改了 JMS 供應(yīng)商而帶來(lái)的影響降至最低,這是十分重要的。JMS 的一個(gè)主要優(yōu)點(diǎn)
          是它以廣泛的業(yè)界支持和開(kāi)放標(biāo)準(zhǔn)為基礎(chǔ),因此有了正確設(shè)計(jì)的代碼,我們就可以讓系
          統(tǒng)使用任何 JMS 系統(tǒng)。(可以對(duì)現(xiàn)有系統(tǒng)進(jìn)行直接改進(jìn),專(zhuān)門(mén)設(shè)計(jì)來(lái)使系統(tǒng)在某套硬件
          上運(yùn)行并能與特定于供應(yīng)商的解決方案相匹配。)
          通過(guò)將所有特定于供應(yīng)商的調(diào)用封裝在稱(chēng)為 JMSProvider 的類(lèi)中,就可以輕松實(shí)現(xiàn)平臺(tái)
          獨(dú)立性。這些 Provider 類(lèi)處理特定于供應(yīng)商的問(wèn)題,例如工廠查詢(xún)、錯(cuò)誤處理、連接
          創(chuàng)建和消息特性設(shè)置等。請(qǐng)參閱下面清單 1 中的示例代碼。
          清單 1. 在類(lèi) ar.jms.JmsProvider 中
          public QueueConnection createConnection() throws JMSException {
          return getConnectionFactory().createQueueConnection(getUserName(),
          getPassword());
          }
          通過(guò)利用“Java 命名和目錄接口(JNDI)”,我們將特定于供應(yīng)商的設(shè)置存儲(chǔ)在一個(gè)資
          源庫(kù)(例如,LDAP 庫(kù))中,這樣實(shí)際代碼就幾乎不需要特定于供應(yīng)商的引用。只需要少
          量特定于供應(yīng)商的代碼來(lái)處理一些特性,但是可以將這樣的代碼限定于一些“適配器”
          類(lèi),并將它保存在應(yīng)用程序代碼之外。請(qǐng)參閱下面清單 2 中的示例代碼。因?yàn)?JMS 被
          設(shè)計(jì)用來(lái)方便地使用 JNDI,所以與其它解決方案相比,這是另一個(gè)直接優(yōu)點(diǎn) ― 配置信
          息的集中存儲(chǔ)不僅可以保存基于文本的信息,而且還可以存儲(chǔ)已配置的對(duì)象。
          清單 2. 在類(lèi) ar.jms.JmsProvider 中
          public final static String
          CONNECTION_FACTORY_LOOKUP_NAME_KEY = "CONNECTION_FACTORY_LOOKUP_NAME";
          public final static
          String FILE_TRANSFER_QUEUE_LOOKUP_NAME_KEY =
          "FILE_TRANSFER_QUEUE_LOOKUP_NAME";
          public final static String
          JMS_PROVIDER_CLASS_KEY = "JMS_PROVIDER_CLASS";
          public void init() throws NamingException {
          InitialContext jndi = createInitialContext();
          initConnectionFactory(jndi);
          initFileTransferQueue(jndi);
          }
          public QueueConnection createConnection() throws JMSException {return
          getConnectionFactory().createQueueConnection(getUserName(),
          getPassword());
          }
          public void initConnectionFactory(InitialContext jndi) throws
          NamingException {
          setConnectionFactory((QueueConnectionFactory)jndi.lookup
          (getProperties().getProperty(CONNECTION_FACTORY_LOOKUP_NAME
          _KEY)));
          }
          public void initFileTransferQueue(InitialContext jndi) throws
          NamingException {
          setFileTransferQueue((Queue) jndi.lookup
          (getProperties().getProperty(FILE_TRANSFER_QUEUE_LOOKUP_NAM
          E_KEY)));
          }
          跳出傳統(tǒng)模式,JMS 解決方案允許以可靠的方式傳送消息,即一旦確認(rèn)已將消息傳送到
          JMS 服務(wù)器,就將它傳送至尋址到的目的地(隊(duì)列)。MQSeries 也不例外。一旦成功
          執(zhí)行了將消息發(fā)送到服務(wù)器的代碼,客戶(hù)機(jī)就保證目的地最終會(huì)接收到消息,即使所討
          論的服務(wù)器在處理過(guò)程中出現(xiàn)故障(如果目的地暫時(shí)不可用,或者 JMS 服務(wù)器死機(jī)等等
          )。請(qǐng)參閱下面清單 3 中的示例代碼。下面代碼中的類(lèi)實(shí)際上負(fù)責(zé)一旦它確定需要發(fā)送
          文件,就執(zhí)行數(shù)據(jù)的發(fā)送。
          通過(guò)將消息配置為持久消息,我們可以保證一旦目的地(隊(duì)列)接收到消息,那么消息
          將保留在那里直到它在該隊(duì)列中被檢索為止 ― 即使在系統(tǒng)有故障期間。因此,一旦安
          全地將消息傳送到本地 JMS 服務(wù)器,就可以刪除它了。不能過(guò)高估計(jì)克服系統(tǒng)故障的能
          力;對(duì)周期性系統(tǒng)故障的處理是開(kāi)發(fā)分布式存檔解決方案最重要的問(wèn)題之一??蛻?hù)現(xiàn)有
          系統(tǒng)上處理故障情況的代碼很復(fù)雜很脆弱,而且對(duì)這些故障的處理和維護(hù)費(fèi)用很高。通
          過(guò)一個(gè)健壯的、經(jīng)測(cè)試成功的商業(yè)解決方案,JMS 使我們能解決所有這些問(wèn)題。
          清單 3. 來(lái)自類(lèi) ar.jms.file.send.ConnectionElement
          public void sendMessage(byte[] payload, boolean persistent) throws
          SendFailedException {
          QueueSender sender = null;
          try {
          Message message = createMessage(payload);
          sender = createSender
          (persistent ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT);

          sender.send(message);
          getClient().getLogService().logInfo(getName() +
          " sent message " + message.getJMSMessageID() + ".");
          } catch (JMSException exception) {
          getClient().getLogService().logError
          ("JMS exception processing " + getName(),exception);
          stop();
          throw new SendFailedException("JMS Message Send Failed");
          }
          try {
          sender.close();
          } catch (JMSException ignore) {
          getClient().getLogService().logInfo(getName() + " failed to
          close sender. Processing will continue.");
          }
          }
          這個(gè)解決方案的關(guān)鍵是配置 JMS 消息和服務(wù)器來(lái)同時(shí)提供令人滿(mǎn)意的性能和服務(wù)質(zhì)量。
          JMS 規(guī)范定義了配置選項(xiàng),并通過(guò)所有商業(yè)解決方案實(shí)現(xiàn)它們。但是,配置的確切方法
          根據(jù)不同的供應(yīng)商而有所不同。
          設(shè)置
          我們創(chuàng)建的體系結(jié)構(gòu)和系統(tǒng)具有通用性且很強(qiáng)大。但是,對(duì)于一些移動(dòng)部件,必須使用
          正確的方式配置并鉤連它們。以下內(nèi)容是有關(guān)將 MQSeries 成功地設(shè)置為 JMS 服務(wù)器的
          概述、一些潛在缺陷和實(shí)際的指示信息。
          對(duì)于 MQSeries,首先設(shè)置 JNDI 服務(wù)器來(lái)檢索特定于實(shí)現(xiàn)的設(shè)置,在這種情況下是 JM
          S 連接工廠(JMS Connection Factory)。有許多不同方法來(lái)實(shí)現(xiàn)這個(gè)操作,但適宜的
          通用選項(xiàng)是輕量級(jí)目錄訪問(wèn)協(xié)議(LDAP)服務(wù)器。我們選擇使用 Qualcomm SLAPD。一旦
          安裝好并運(yùn)行該服務(wù)器,就可以用 MQSeries 管理工具(JMSAdmin.bat)來(lái)設(shè)置該服務(wù)
          器并將其作為 MQ 對(duì)象信息庫(kù)來(lái)使用。請(qǐng)參閱參考資料,獲取有關(guān)講述該過(guò)程的實(shí)用書(shū)
          籍的鏈接。同時(shí),在設(shè)置期間,要特別注意在 IBM MQSeries 之上設(shè)置 JMS 的 IBM 文
          檔,這很重要。這個(gè)過(guò)程涉及創(chuàng)建一些隊(duì)列和其它對(duì)象,這些隊(duì)列和對(duì)象是特定于 JMS
          使用并且不屬于標(biāo)準(zhǔn) MQSeries 安裝的。
          完成 JNDI/LDAP 和 JMS 服務(wù)器的設(shè)置后,就可以準(zhǔn)備配置客戶(hù)機(jī)了。第一步是理解 J
          MS 如何與 IBM 的標(biāo)準(zhǔn) MQSeries 實(shí)現(xiàn)交互。MQSeries 的 Java 客戶(hù)機(jī)能以?xún)煞N模式之
          一進(jìn)行交互:客戶(hù)機(jī)和綁定模式。只能通過(guò) Java Applet 來(lái)使用客戶(hù)機(jī)模式,而綁定模
          式取決于客戶(hù)機(jī)上的 DLL 或者對(duì)象庫(kù)。因?yàn)閷?shí)現(xiàn)的特性,當(dāng)使用用于 JMS 連接信息的
          LDAP 服務(wù)器時(shí),只能使用綁定模式。(不清楚為什么有這個(gè)限制,但它確實(shí)存在。)
          因此,將用戶(hù)登錄和密碼存儲(chǔ)在一個(gè)全局位置(com.ibm.mq.MQEnvironment.class)而
          不是在連接時(shí)傳遞它們。要解決這些供應(yīng)商問(wèn)題,我們創(chuàng)建了標(biāo)準(zhǔn) JmsProvider 類(lèi)(稱(chēng)
          為 MQSeriesProvider)的子類(lèi)。這個(gè)類(lèi)將完成的唯一操作是覆蓋如何創(chuàng)建連接。不象清
          單 1 中那樣
          newQueueConnection = getConnectionFactory().createQueueConnection(getUserNam
          e(),getPassword));
          進(jìn)行調(diào)用,我們必須調(diào)用
          newQueueConnection = getConnectionFactory().createQueueConnection();
          最后,您需要將特定于 JMS 的元素(如隊(duì)列、隊(duì)列管理器、隊(duì)列工廠等等)提供給客戶(hù)
          機(jī)。現(xiàn)在,使用 LDAP 和 JNDI 的原因就變得很明顯:我們使用 LDAP 服務(wù)器來(lái)存儲(chǔ)這
          些元素并使用外部文件保存那些 LDAP 對(duì)象的鍵。LDAP 服務(wù)器可以作為 JNDI 服務(wù)器并
          通過(guò)返回存儲(chǔ)的對(duì)象來(lái)響應(yīng)名稱(chēng)查詢(xún)。這就是清單 2 中代碼的工作原理。JMS 元素的名
          稱(chēng)可從類(lèi)的靜態(tài)變量(對(duì)于缺省名稱(chēng))或者外部文件(使用非缺省的其它名稱(chēng))中獲取
          。簡(jiǎn)而言之,向 LDAP 服務(wù)器請(qǐng)求鍵(我們正討論的)中存儲(chǔ)的對(duì)象,并返回我們感興
          趣的 JMS 對(duì)象(在這種情況下)。
          我們基于 JMS 的解決方案通過(guò)使用現(xiàn)有的組件更方便地實(shí)現(xiàn)統(tǒng)一的、跨平臺(tái)和交叉供應(yīng)
          商的配置環(huán)境?,F(xiàn)在我們的代碼已盡可能地成為獨(dú)立于特定于平臺(tái)和特定于供應(yīng)商的設(shè)
          置。
          應(yīng)用程序
          應(yīng)用程序中有兩個(gè)關(guān)鍵組件:發(fā)送器和接收器。發(fā)送器啟動(dòng)一個(gè)后臺(tái)程序,它在目錄中
          輪詢(xún)需要存檔的文件,而接收器只是等待將要傳遞的 JMS 消息,然后將該消息中包含的
          文件存檔。JMS API 使我們幾乎無(wú)需關(guān)注正使用的特定 JMS 實(shí)現(xiàn)就可以定義這些組件。

          發(fā)送器由三個(gè)主要部件組成:
          JMSProvider,用于創(chuàng)建連接
          ConnectionPool,用于獲取現(xiàn)有的空閑連接(我們稱(chēng)之為 JMSConnection)
          輪詢(xún)程序,監(jiān)視需要傳送的文件。
          在啟動(dòng)時(shí),使用 JMSProvider 來(lái)創(chuàng)建一些到 JMS 服務(wù)器的準(zhǔn)備就緒的連接。這些連接
          放置在池中,然后啟動(dòng)輪詢(xún)程序。當(dāng)輪詢(xún)程序檢測(cè)到需要傳送文件時(shí),它就創(chuàng)建一個(gè)獨(dú)
          立線程來(lái)處理這個(gè)文件。(可以通過(guò)派生(forking),創(chuàng)建一個(gè)獨(dú)立的線程來(lái)創(chuàng)建消息
          和進(jìn)行傳送操作,描述該過(guò)程非常簡(jiǎn)單。但在實(shí)際應(yīng)用中,常常將合用(pooling)與循
          環(huán)組合起來(lái)使用,這樣可以確保很少創(chuàng)建新線程,而是重用線程。但是,那個(gè)過(guò)程相當(dāng)
          復(fù)雜,過(guò)多的說(shuō)明會(huì)分散本文的中心主題 ― JMS。)
          在獨(dú)立線程中,輪詢(xún)程序接著從連接池中獲取 JMSConnection,用它來(lái)創(chuàng)建一個(gè) Bytes
          Message,并將這個(gè)文件的二進(jìn)制內(nèi)容放入那個(gè)消息中。最后這個(gè)消息查找到接收器,并
          發(fā)送到 JMS 服務(wù)器,接著將 JMSConnection 返回給 ConnectionPool。這個(gè)發(fā)送過(guò)程的
          部分步驟顯示在下面的圖 2 中。
          圖 2. 發(fā)送器過(guò)程
          接收器是一個(gè)較簡(jiǎn)單的組件;它啟動(dòng)一些 FileListener 來(lái)等待將要放置在接收器隊(duì)列
          中的消息。下面的清單 4 中的代碼顯示了 FileListener 設(shè)置處理過(guò)程。圖 6 中的類(lèi)
          實(shí)際上負(fù)責(zé)從隊(duì)列中檢索消息并對(duì)它們進(jìn)行存檔。JMS 保證隊(duì)列發(fā)送每個(gè)消息的次數(shù)僅
          一次,所以我們可以安全啟動(dòng)許多不同的 FileListener 線程并且知道每個(gè)消息(因此
          每個(gè)文件)只處理一次。這個(gè)保證是使用基于 JMS 解決方案的另一個(gè)重要優(yōu)點(diǎn)。在自己
          設(shè)計(jì)的解決方案中開(kāi)發(fā)這樣的功能(比如基于 FTP 的功能),花銷(xiāo)很大且易出錯(cuò)。
          清單 4:來(lái)自類(lèi) ar.jms.file.receive.FileListener public void startOn(Queue qu
          eue) {
          setQueue(queue);
          createConnection();
          try {
          createSession();
          createReceiver();
          getConnection().start(); // this starts
          the queue listener
          } catch (JMSException exception) {
          // Handle the exception
          }
          }
          public void createReceiver() throws javax.jms.JMSException {
          try {
          QueueReceiver receiver = getSession().
          createReceiver(getQueue());
          receiver.setMessageListener(this);
          } catch (JMSException exception) {
          // Handle the exception
          }
          }
          public void createSession() throws JMSException {
          setSession(getConnection().
          createQueueSession(false, Session.AUTO_ACKNOWLEDGE));
          }
          public void createConnection() {
          while (!hasConnection()) {
          try {
          setConnection(getClient().createConnection());
          } catch (JMSException exception) {
          // Connections drop periodically on the
          internet, log and try again.
          try {
          Thread.sleep(2000);
          } catch
          (java.lang.InterruptedException ignored) {
          }
          }
          }
          }
          以回調(diào)的方式編寫(xiě)消息處理代碼,回調(diào)是當(dāng)將消息傳遞給 FileListener 時(shí),JMS 自動(dòng)
          調(diào)用的方法。這個(gè)消息的代碼顯示在下面的清單 5 中。
          清單 5. 來(lái)自類(lèi) ar.jms.file.receive.FileListener public void onMessage(Messag
          e message) {
          BytesMessage byteMessage = ((BytesMessage) message);
          OutputStream stream =
          new BufferedOutputStream(
          new FileOutputStream(getFilenameFor(message)));
          byte[] buffer = new byte[getFileBufferSize()];
          int length = 0;
          try {
          while ((length = byteMessage.readBytes(buffer)) != -1) {
          stream.write(buffer, 0, length);
          }
          stream.close();
          } catch (JMSException exception) {
          // Handle the JMSException
          } catch (IOException exception) {
          // Handle the IOException
          }
          }
          在設(shè)置接收器時(shí)要記住一條訣竅:在所有 FileListener 啟動(dòng)后,確保啟動(dòng)這些 FileL
          istener 的原始線程繼續(xù)運(yùn)行。這是必要的,因?yàn)槟承?JMS 實(shí)現(xiàn)在守護(hù)程序的線程中啟
          動(dòng) QueueListener。所以,如果正在運(yùn)行的唯一線程是守護(hù)程序的線程,那么 Java 虛
          擬機(jī)(JVM)可能會(huì)過(guò)早地意外退出。下面的清單 6 顯示了一些防止這種情況發(fā)生的簡(jiǎn)
          單代碼。
          清單 6. 至少使一個(gè)非守護(hù)程序的線程保持運(yùn)行 public static void main(String[]
          args) {
          ReceiverClient newReceiverClient = new ReceiverClient();
          newReceiverClient.init();
          setSoleInstance(newReceiverClient);
          while(!finished) { // This prevents the VM from exiting early
          try {
          Thread.sleep(1000);
          } catch (InterruptedException ex) {
          }
          }
          }
          結(jié)束語(yǔ)
          在該項(xiàng)目的最初實(shí)現(xiàn)之后,我們添加了一些功能,象消息壓縮、當(dāng)位置無(wú)法到達(dá)時(shí)的自
          動(dòng)恢復(fù)、聯(lián)合消息代理、安全性、健壯的日志記錄、管理等等。添加這些功能很容易,
          因?yàn)?JMS 提供了開(kāi)放模型,而且我們的體系結(jié)構(gòu)也很健壯。構(gòu)建整個(gè)系統(tǒng)花了六個(gè)星期
          的時(shí)間,并且還很快地替換了客戶(hù)一直使用的現(xiàn)有的、勞動(dòng)密集型的系統(tǒng)。在這些天里
          ,系統(tǒng)已經(jīng)超出了所有的基準(zhǔn)測(cè)試程序的標(biāo)準(zhǔn)并且已更正了原來(lái)系統(tǒng)遺留下來(lái)的錯(cuò)誤。
          這個(gè)項(xiàng)目不單超出了客戶(hù)的期望,還證明了 JMS 是一個(gè)可行的解決方案,不僅適用于小
          型、面向消息的應(yīng)用程序,而且還適用于大規(guī)模的、重要任務(wù)的數(shù)據(jù)傳送操作。
          參考資料
          獲取更多信息或者下載 Qualcomm slapd。
          訪問(wèn) JMS 主頁(yè)。
          回顧 FTP 的安全性問(wèn)題。
          查閱 Giotta 等人編寫(xiě)的 JMS 設(shè)置一書(shū):“Professional JMS Programming”。
          學(xué)習(xí)有關(guān) IBM MQSeries 的更多內(nèi)容或者下載測(cè)試版本。
          閱讀 developerWorks 上的相關(guān)文章:“Java theory and practice: Should you use
          JMS in your next entERPrise application?”。
          閱讀 developerWorks 上 Daniel 以前的文章:“The profound impact of hardware,
          software, and networking decisions”。

          posted on 2007-05-06 12:55 張金鵬 閱讀(190) 評(píng)論(0)  編輯  收藏

          只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 五莲县| 盘锦市| 定西市| 禄丰县| 临沭县| 天柱县| 苍山县| 兴义市| 安图县| 渑池县| 泰宁县| 保定市| 方山县| 东阳市| 丰台区| 安龙县| 阿拉善左旗| 福贡县| 武陟县| 马边| 墨脱县| 峨山| 鞍山市| 泰顺县| 镇远县| 凤翔县| 金沙县| 武川县| 英山县| 资中县| 泽库县| 霞浦县| 星子县| 福州市| 大冶市| 龙门县| 沾化县| 确山县| 镶黄旗| 南木林县| 信阳市|