1. 程式人生 > >構建伺服器叢集感知的 Java 應用程式-http://www.ibm.com/developerworks/cn/java/j-zookeeper/

構建伺服器叢集感知的 Java 應用程式-http://www.ibm.com/developerworks/cn/java/j-zookeeper/

在 IBM Bluemix 雲平臺上開發並部署您的下一個應用。現在就開始免費試用

如今,許多企業應用程式都由一組合作的分散式程序和伺服器交付。例如,可向幾乎所有流行的 Java 企業伺服器的 Web 請求提供伺服器叢集功能,這些伺服器還可以提供有限的配置選項,如伺服器權重和配置重新載入。

雖然大多數 Java 企業伺服器具有叢集的內建支援,但對於自定義用例來說,在應用程式級並沒有現成提供這種支援。作為軟體開發人員,我們應該如何管理涉及分散式任務協調或支援多租戶應用程式的用例?(多租戶應用程式 是要求例項在整體伺服器叢集或組的子集上被隔離的應用程式。)對於這些型別的用例,我們必須找到一種方法,使得組協調功能在應用程式軟體層上可用,最好能在高級別的抽象層上提供。

在本文中,我們提供了將組成員和管理功能融合到分散式 Java 應用程式中的指南。我們將從一個基於 Spring Integration 的模擬 Java 應用程式開始,利用基於兩個開源專案(Apache ZooKeeper 和 LinkedIn 的 Project Norbert)的伺服器叢集抽象層來進行構建。

關於 ZooKeeper 和 Project Norbert

Apache ZooKeeper 是一個開源專案(參見 參考資料),向分散式應用程式提供伺服器組協調功能。由 LinkedIn 開發的 Project Norbert 在更高的抽象層次上揭示 ZooKeeper 的伺服器組管理和請求路由功能,從而使 Java 應用程式開發人員更易於訪問它們。

伺服器叢集概述

伺服器叢集感知的應用程式通常至少需要以下功能:

  • 具有狀態維護和查詢功能的組成員:需要實時組成員,以便在一組活動的伺服器上分發處理。為了管理組成員,應用程式必須能夠建立一個程序/伺服器組,並跟蹤該組中所有伺服器的狀態。當某臺伺服器停機或上線時,它還必須能夠通知活動的伺服器。該應用程式將只在叢集中的活動伺服器之間對服務請求執行路由和負載均衡,從而幫助確保實現高可用的服務。
  • 主要程序或領袖程序:這是一個在叢集中的程序,負責維護整個伺服器叢集的同步狀態的協調功能。選擇領袖程序的機制是被稱為分散式共識 的一組更廣泛的問題中的一個特例。(兩階段提交和三階段提交是眾所周知的分散式共識問題。)
  • 任務協調和動態的領袖伺服器選舉:在應用程式級別,領袖伺服器 負責任務協調,通過叢集中的其他(跟隨者)伺服器之間分發任務來做到這一點。擁有一臺領袖伺服器,可以消除伺服器之間潛在的爭用,否則爭用將需要某種形式的互斥或鎖定,才可以使符合資格的任務執行(例如,伺服器對來自公共資料儲存的任務進行輪詢)。正是動態領袖選舉使得分散式處理變得可靠;如果領袖伺服器崩潰,可以選舉新的領袖繼續處理應用程式任務。
  • 組通訊:在一個叢集感知的應用程式中的應用程式應該能夠在整個伺服器叢集中促進結構化資料和命令的高效交換。
  • 分散式鎖和共享資料:如果有需要,分散式應用程式應該能夠訪問的特性包括分散式鎖和共享資料結構,如佇列和對映。

示例:Spring Integration

我們的代表示例是一個企業應用程式整合 (EAI) 場景,我們將利用基於 Spring Integration 的模擬應用程式處理該場景。該應用程式具有以下特徵和要求:

  1. 一個模擬源應用程式產生與整合相關的事件和訊息作為其日常事務處理的一部分,並將它們儲存在一個數據儲存中。
  2. 整合事件和訊息由一組分散式 Java 程序(一個伺服器叢集)進行處理,這些程序可以在同一臺伺服器上執行,也可以分佈在由一個高效能網路連線的多臺計算機上。需要伺服器叢集來實現可擴充套件性和高可用性。
  3. 每個整合事件只由任一叢集成員(即特定的 JVM)處理一次。輸出訊息通過 Intranet 或 Internet 被傳遞給合作伙伴應用程式(如果適用)。

圖 1 顯示了整合事件和從模擬源應用程式出站的訊息處理流。

圖 1. 基於 Spring Integration 的示例應用程式示意圖
基於 Spring Integration 的示例應用程式示意圖

設計解決方案的架構

要為該用例開發一個解決方案,第一步是分發要在一個伺服器叢集上執行的整合應用程式。這應該能增加處理吞吐量,並能確保高可用性和可擴充套件性。單次程序的失敗不會中止整個應用程式。

一旦完成分配,整合應用程式將從應用程式的資料儲存中獲得整合事件。伺服器叢集中的單臺伺服器將通過一個合適的應用程式介面卡從事件儲存獲取應用程式事件,然後將這些事件分發給叢集中的其餘伺服器進行處理。這個單臺伺服器擔任領袖伺服器,或任務協調者 的角色,負責將事件(處理任務)分發給整個叢集中的其餘伺服器。

支援整合應用程式的伺服器叢集成員在配置時已眾所周知。每當有新伺服器啟動,或有任何伺服器崩潰或停機時,叢集成員資訊就會動態分發給所有執行的伺服器。同樣,任務協調者伺服器也是動態選擇的,如果任務協調者伺服器崩潰或變得不可用,將從其餘執行中的伺服器中以合作方式選一個備用的領袖伺服器。整合事件可能由支援企業整合模式 (Enterprise Integration Patterns, EIP) 的多個開源 Java 框架其中之一處理(參見 參考資料)。

圖 2 顯示了用例解決方案的示意圖和元件,我們會在下一節中進一步描述它們。

圖 2. 用例解決方案的示意圖和伺服器叢集元件
用例解決方案的示意圖和伺服器叢集元件

伺服器叢集

我們的整合應用程式需要伺服器組相關的特性,但是無論是 Java 標準版 (Java SE) 還是 Java 企業版 (Java EE) 均沒有現成提供這些特性。這些示例包括伺服器叢集和動態伺服器領袖選舉。

圖 3 顯示了我們將用於實施我們的 EAI 解決方案的開源工具,即 Spring Integration 實現事件處理,以及 Apache ZooKeeper 和 LinkedIn Project Norbert 實現叢集感知。

圖 3. 伺服器叢集的技術對映
伺服器叢集的技術對映

關於模擬的應用程式

模擬應用程式的目的是演示如何使用 Apache ZooKeeper 和 Project Norbert 來解決在開發基於 Java 的伺服器叢集中所面臨的常見挑戰。該應用程式的工作原理如下:

  • 應用程式事件儲存由一個共享資料夾來模擬,整合伺服器叢集內的所有伺服器都可以訪問該資料夾。
  • 使用儲存在此共享資料夾上的檔案(以及其資料)來模擬整合事件。也可以使用外部指令碼來不斷建立檔案,從而模擬事件的建立。
  • 基於 Spring Integration 的檔案輪詢元件(入站事件介面卡)從應用程式事件儲存獲取事件,這是由共享檔案系統資料夾模擬的事件。然後,檔案資料被分發到其餘的伺服器叢集成員進行處理。
  • 事件處理的模擬方法是:用簡短的標頭型別資訊(比如 server id 和 timestamp)作為檔案資料的字首。
  • 合作伙伴應用程式由其他一些獨特的共享檔案系統模擬資料夾,每個合作伙伴應用程式對應一個資料夾。

現在已概述過示例用例、建議的解決方案架構,以及模擬的應用程式。現在,我們準備介紹伺服器叢集和任務分發解決方案的兩個主要元件:Apache ZooKeeper 和 LinkedIn 的 Project Norbert。

Apache ZooKeeper 和 Project Norbert

首先由 Yahoo Research 開發的 ZooKeeper 最初被 Apache Software Foundation 採納為 Hadoop 的一個子專案。如今,它已是一個頂級專案,可提供分散式組協調服務。我們將使用 ZooKeeper 建立伺服器叢集來託管我們的模擬應用程式。ZooKeeper 也將實現應用程式所需要的伺服器領袖選舉功能。(領袖選舉對於 ZooKeeper 提供的所有其他組協調功能是必不可少的。)

ZooKeeper 伺服器通過 znode(ZooKeeper 資料節點)實現伺服器協調,znode 是在記憶體中複製的類似於分層檔案系統的資料模型。和檔案一樣,znode 可以儲存資料,但它們也像目錄一樣,可以包含子級 znode。

有兩種型別的 znode:常規 znode 由客戶端程序顯式建立和刪除,而 暫時性 znode 在發起的客戶端會話停止時會自動被刪除。若常規或暫時性 znode 在建立時帶有順序標誌,一個 10 位數字的單調遞增字尾將被附加到 znode 名稱。

關於 ZooKeeper 的更多資訊:

  • ZooKeeper 確保當伺服器啟動時,每臺伺服器都知道該組中其他伺服器的偵聽埠。偵聽埠 支援促進領袖選舉、對等通訊,以及客戶端與伺服器的連線的服務。
  • ZooKeeper 使用一個組共識演算法來選舉領袖,完成選舉之後,其他伺服器都稱為跟隨者。伺服器叢集需要伺服器數量達到下限(法定數量)時才可以執行。
  • 客戶端程序有一組已定義的可用操作,他們使用這些操作基於 znode 編排資料模型的讀取和更新。
  • 所有寫操作都通過領袖伺服器進行路由,這限制了寫操作的可擴充套件性。領袖使用稱為 ZooKeeper Atomic Broadcast (ZAB) 的廣播協議來更新跟隨者伺服器。ZAB 保留更新順序。因此,在記憶體中的類似於檔案系統的資料模型最終在組或叢集中的所有伺服器上保持同步。資料模型也通過持久的快照被定期寫入到磁碟。
  • 讀操作的可擴充套件性比寫操作高得多。跟隨者從資料模型的這個同步副本響應客戶端程序讀取。
  • znode 支援客戶端的一次性回撥機制,被稱為 “看守者”。看守者觸發一個監視客戶端程序的訊號,監視被看守的 znode 的更改。

利用 Project Norbert 實現組管理

LinkedIn 的 Project Norbert 掛接到一個基於 Apache ZooKeeper 的叢集,以提供具有伺服器叢集成員感知的應用程式。Norbert 在執行時動態完成該操作。Norbert 也封裝了一個 JBoss Netty 伺服器,並提供了相應的客戶端模組來支援應用程式交換訊息。需要注意的是,早期版本的 Norbert 需要使用 Google Protocol Buffers 物件序列化訊息庫。目前的版本支援自定義物件的序列化。(請參閱 參考資料 瞭解更多資訊。)

Spring Integration

Spring Integration 是一個解決 EAI 挑戰的開源框架。它通過宣告元件支援 Enterprise Integration Patterns(企業整合模式),並建立在 Spring 程式設計模型之上。

構建一個伺服器叢集

準備好所有元件之後,我們就可以開始配置事件處理伺服器叢集了。配置叢集的第一步是建立伺服器法定數量,之後,新當選的領袖伺服器會自動啟動本地檔案輪詢流。檔案輪詢通過 Spring Integration 發生,Spring Integration 模擬一個入站應用程式事件介面卡。使用一個迴圈任務分配策略,將輪詢過的檔案(模擬應用程式事件)分發到可用的伺服器中。

需要注意的是,ZooKeeper 將有效的法定數量定義為伺服器程序的大多數。因此,一個叢集至少由三個伺服器組成,在至少兩個伺服器處於活動狀態時建立法定數量。此外,在我們的模擬應用程式中,每臺伺服器都需要兩個配置檔案:一個屬性檔案,由引導整個伺服器 JVM 的驅動程式使用,還有一個單獨的屬性檔案供基於 ZooKeeper 的伺服器叢集(在該叢集中,每個伺服器都是一個部分)使用。

第 1 步:建立一個屬性檔案

Server.java(參見 參考資料)是一個控制器和條目類,用來啟動我們的分散式 EAI 應用程式。應用程式的初始引數是從屬性檔案中讀取的,如 清單 1 所示:

清單 1. 伺服器屬性檔案
# Each server in group gets a unique id:integer in range 1-255  
server.id=1

# zookeeper server configuration parameter file -relative path to this bootstrap file
zookeeperConfigFile=server1.cfg

#directory where input events for processing are polled for - common for all servers
inputEventsDir=C:/test/in_events

#directory where output / processed events are written out - may or may not be shared by 
#all servers
outputEventsDir=C:/test/out_events

#listener port for Netty server (used for intra server message exchange)
messageServerPort=2195

注意,在這個最小的伺服器叢集中,每一臺伺服器都需要一個惟一的 server id(整數值)。

輸入事件目錄被所有伺服器共享。輸出事件目錄模擬一個合作伙伴應用程式,並可以視情況由所有伺服器共享。ZooKeeper 分發提供了一個類,用於解析伺服器叢集的每個成員伺服器或 “法定數量對等伺服器” 的配置資訊。因為我們的應用程式重用了這個類,所以它需要相同格式的 ZooKeeper 配置。

還需要注意的是,messageServerPort 是 Netty 伺服器(由 Norbert 庫啟動和管理)的偵聽器埠。

第 2 步:為程序中的 ZooKeeper 伺服器建立一個配置檔案

清單 2. ZooKeeper 的配置檔案 (server1.cfg)
tickTime=2000
dataDir=C:/test/node1/data
dataLogDir=C:/test/node1/log
clientPort=2181
initLimit=5
syncLimit=2
peerType=participant
maxSessionTimeout=60000
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890

ZooKeeper 文件(參見 參考資料)中介紹了在 清單 2 中顯示的引數(以及一些可選引數,這些引數均使用預設值,覆蓋的引數除外)。需要注意的是,每個 ZooKeeper 伺服器使用三個偵聽器埠。clientPort(在上面的配置檔案中是 2181)由連線到伺服器的客戶端程序使用;第二個偵聽器埠用於實現對等通訊(對於伺服器 1 的值是 2888);第三個偵聽器埠支援領袖選舉協議(對於伺服器 1 的值是 3888)。每臺伺服器都支援叢集的整體伺服器拓撲結構,所以 server1.cfg 也列出了伺服器 2 和伺服器 3 及其對等埠。

第 3 步:在伺服器啟動時初始化 ZooKeeper 叢集

控制器類 Server.java 啟動一個單獨的的執行緒 (ZooKeeperServer.java),該執行緒封裝基於 ZooKeeper 的叢集成員,如 清單 3 所示:

清單 3. ZooKeeperServer.java
package ibm.developerworks.article;
…
public class ZooKeeperServer implements Runnable
{

   public ZooKeeperServer(File configFile) throws ConfigException, IOException
   {
      serverConfig = new QuorumPeerConfig();
      …
      serverConfig.parse(configFile.getCanonicalPath());
    }

      public void run()
   {
      NIOServerCnxn.Factory cnxnFactory;
      try
      {
         // supports client connections
         cnxnFactory = new NIOServerCnxn.Factory(serverConfig.getClientPortAddress(),
               serverConfig.getMaxClientCnxns());
         server = new QuorumPeer();

         // most properties defaulted from QuorumPeerConfig; can be overridden
         // by specifying in the zookeeper config file

         server.setClientPortAddress(serverConfig.getClientPortAddress());
         …
         server.start(); //start this cluster member

         // wait for server thread to die
         server.join();
      }
      …
   }

    …
   public boolean isLeader()
   {
      //used to control file poller.  Only the leader process does task
      // distribution
      if (server != null)
      {
         return (server.leader != null);
      }
      return false;
   }

第 4 步:初始化基於 Norbert 的訊息傳送伺服器

建立了伺服器法定數量後,我們可以啟動基於 Norbert 的 Netty 伺服器,該伺服器支援快速的伺服器內部訊息傳送。

清單 4. MessagingServer.java
   public static void init(QuorumPeerConfig config) throws UnknownHostException
   {
       …
      // [a] client (wrapper) for zookeeper server - points to local / in process
      // zookeeper server
      String host = "localhost" + ":" + config.getClientPortAddress().getPort();

      //[a1] the zookeeper session timeout (5000 ms) affects how fast cluster topology 
      // changes are communicated back to the cluster state listener class

      zkClusterClient = new ZooKeeperClusterClient("eai_sample_service", host, 5000);

      zkClusterClient.awaitConnectionUninterruptibly();
      …
      // [b] nettyServerURL - is URL for local Netty server URL
      nettyServerURL = String.format("%s:%d", InetAddress.getLocalHost().getHostName(),
            Server.getNettyServerPort());
      …

      // [c]
      …
      zkClusterClient.addNode(nodeId, nettyServerURL);

      // [d] add cluster listener to monitor state
      zkClusterClient.addListener(new ClusterStateListener());

      //  Norbert - Netty server config
      NetworkServerConfig norbertServerConfig = new NetworkServerConfig();

      // [e] group coordination via zookeeper cluster client
      norbertServerConfig.setClusterClient(zkClusterClient);

      // [f] threads required for processing requests
      norbertServerConfig.setRequestThreadMaxPoolSize(20);

      networkServer = new NettyNetworkServer(norbertServerConfig);

      // [g] register message handler (identifies request and response types) and the
      // corresponding object serializer for the request and response
      networkServer.registerHandler(new AppMessageHandler(), new CommonSerializer());

      // bind the server to the unique server id
      // one to one association between zookeeper server and Norbert server
      networkServer.bind(Server.getServerId());   

   }

請注意,基於 Norbert 的訊息傳送伺服器包括一個連線到 ZooKeeper 叢集的客戶端。配置此伺服器,連線到本地(程序中)的 ZooKeeper 伺服器,然後為 ZooKeeper 伺服器建立一個客戶端。會話超時將影響叢集的拓撲結構更改可以多快傳回應用程式。這將有效地建立一個較小的時間視窗,在該時間視窗內,叢集拓撲結構的記錄狀態將與叢集拓撲結構的實際狀態不同步,這是因為新的伺服器啟動或現有的伺服器崩潰。應用程式需要在這段時間內緩衝訊息或實施訊息傳送失敗的重試邏輯。

MessagingServer.java (清單 4) 執行以下任務:

  • 為 Netty 伺服器配置端點 (URL)。
  • 將本地 node Id 或 server Id 與已配置的 Netty 伺服器相關聯。
  • 關聯一個叢集狀態偵聽器(我們將會簡略介紹)例項。Norbert 將使用該例項將叢集拓撲結構更改推送回應用程式。
  • 將 ZooKeeper 叢集客戶端分配給正在進行填充的伺服器配置例項。
  • 為請求/響應對關聯一個惟一的訊息處理程式類。也需要一個序列化程式類來對請求和響應物件進行編組和解組。(您可以在 GitHub 上訪問相應的類,其中包括解決方案程式碼,具體參見 參考資料)連結。

還要注意的是,訊息傳送的應用程式回撥需要一個執行緒池。

第 5 步:初始化 Norbert 叢集客戶端

接下來,初始化 Norbert 叢集客戶端。MessagingClient.java(如 清單 5 所示)配置叢集客戶端,並使用一個負載均衡策略初始化該客戶端:

清單 5. MessagingClient.java
public class MessagingClient
{
   …
   public static void init()
   {
      …
      NetworkClientConfig config = new NetworkClientConfig();

      // [a] need instance of local norbert based zookeeper cluster client
      config.setClusterClient(MessagingServer.getZooKeeperClusterClient());

      // [b] cluster client with round robin selection of message target
      nettyClient = new NettyNetworkClient(config, 
                                 new RoundRobinLoadBalancerFactory());
      …
   }
   ...
    …
   // [c] – if server id <0 – used round robin strategy to choose target
   // else send to identified target server 
   public static Future<String> sendMessage(AppRequestMsg messg, int serverId)
         throws Exception
   {
      …
      // [d] load balance message to cluster- strategy registered with client
      if (serverId <= 0)
      {
         …
         return nettyClient.sendRequest(messg, serializer);
      }
      else
      {
         // [e] message to be sent to particular server node
         …
         if (destNode != null)
         {
            …
            return nettyClient.sendRequestToNode(messg, destNode, serializer);
         }
         …
      }
   }
   …
}

注意,在 清單 5 中,如果沒有由一個正的 server Id 值識別目標伺服器,根據已配置的負載均衡策略從活動的組中選擇伺服器會發出訊息。應用程式可以配置和實現它們自己的訊息處理策略,也許以其他伺服器屬性為依據。(考慮多租戶應用程式,其中的請求可以轉發到已識別的伺服器子叢集,每個租戶對應一個子叢集;參見 參考資料 瞭解更多討論。)

狀態監測和任務分發

模擬的應用程式還有三個元件,我們將在下面的章節中進行介紹:

  • 一個元件用於監視叢集的狀態(伺服器成員)。
  • 一個 Spring Integration 流定義檔案。程序定義檔案定義基於 EIP 的訊息流,包括從模擬應用程式的任務池流到中心任務分發程式。任務分發程式最終將每個任務路由到其中一個可用的叢集成員,以進行處理。
  • 一個任務分發程式,該程式實施最終的任務路由,將任務路由到其中一個叢集成員。

叢集狀態(拓撲結構)偵聽器

叢集狀態偵聽器 確保訊息傳送客戶端擁有最新的可用節點列表。該偵聽器也在領袖伺服器上啟動惟一的事件介面卡例項(檔案輪詢程式)。檔案輪詢程式將輪詢過的檔案移交給訊息處理器元件(一個 POJO),這就是實際的任務分發程式。由於叢集內只有一個任務分發程式例項,所以不需要進一步的應用程式級同步。清單 6 顯示了叢集狀態偵聽器:

清單 6. ClusterStateListener.java
public class ClusterStateListener implements ClusterListener
{
   …
   public void handleClusterNodesChanged(Set<Node> currNodeSet)
   {
      …
      // [a] start event adapter if this server is leader
      if (Server.isLeader() && !Server.isFilePollerRunning())
      {
         Server.startFilePoller();
      }

   }
   …
}

基於 Spring Integration 的檔案輪詢程式

Spring Integration 流執行以下任務(如 清單 7 所示):

  • 建立一條稱為 messageInputChannel 的訊息通道或管道。
  • 定義一個入站通道介面卡,每隔 10 秒輪詢一次從 JVM 系統屬性讀取的目錄(即 property input.dir)中的檔案。被輪詢和定位的任何檔案都被向下傳遞給訊息通道 messageInputChannel
  • 配置任務分發程式 Java Bean,用於從訊息通道接收訊息。呼叫其方法 processMessage 來執行任務分發函式。
清單 7. 基於 Spring Integration 的流:FilePoller_spring.xml
…
   <integration:channel id="messageInputChannel" />

    <int-file:inbound-channel-adapter channel="messageInputChannel"
        directory="file:#{ systemProperties['input.dir'] }"
        filename-pattern="*.*" prevent-duplicates="true" >

        <integration:poller id="poller" fixed-rate="10" />
    </int-file:inbound-channel-adapter>

    <integration:service-activator input-channel="messageInputChannel"
        method="processMessage" ref="taskDistributor" />

    <bean
        id="taskDistributor"
        class="ibm.developerworks.article.TaskDistributor" >
    </bean>
…

任務分發程式

任務分發程式包含跨叢集成員路由請求的邏輯。檔案輪詢程式元件僅在領袖伺服器上啟用,並且將輪詢過的檔案(在本例中為模擬的整合事件)傳遞到任務分發程式。任務分發程式使用 Norbert 客戶端模組,將請求(即封裝成訊息的輪詢過的檔案)路由到叢集中的活動伺服器。任務分發程式如 清單 8 所示:

清單 8. Spring Integration 流控制的任務分發程式(一個 Java Bean)
{
   …
   // [a] invoked by spring integration context 
   public void processMessage(Message<File> polledMessg)
   {
      File polledFile = polledMessg.getPayload();
      …
      
      try
      {
         logr.info("Received file as input:" + polledFile.getCanonicalPath());

         // prefix file name and a delimiter for the rest of the payload
         payload = polledFile.getName() + "|~" + Files.toString(polledFile, charset);

         …
         // create new message
         AppRequestMsg newMessg = new AppRequestMsg(payload);

         // [b]load balance the request to operating servers without
         // targeting any one in particular
         Future<String> retAck = MessagingClient.sendMessage(newMessg, -1);

         // block for acknowledgement - could have processed acknowledgement
         // asynchronously by repositing to a separate queue
         String ack = retAck.get();
         …
         logr.info("sent message and received acknowledgement:" + ack);
      …
   }
}

請注意,服務啟用器 方法的呼叫是通過在檔案輪詢程式找到一個檔案進行處理之後控制 Spring Integration 上下文來完成。另外請注意,檔案的內容是序列化的,並形成一個新請求物件的有效負載。訊息傳送客戶端的 sendMessage() 方法被呼叫,但沒有針對某一特定的目標伺服器。然後,Norbert 客戶端模組將結果訊息負載均衡到叢集的其中一臺執行的伺服器。

執行模擬的應用程式

一個 “可執行” 的 JAR 檔案 (ZooKeeper-Norbert-simulated-app.jar) 與三個伺服器叢集的樣例配置檔案都包含在本文的原始碼中(參見 參考資料)。

若要測試應用程式,您可以在本地的單臺計算機上啟動所有三個伺服器,或在整個網路中分發它們。為了在多臺計算機上執行應用程式,需要一個可從網路安裝/訪問的公共輸入事件資料夾。通過建立相應的配置檔案,每增加一臺伺服器就建立兩個配置檔案,並更新現有的配置檔案之後,您就可以在叢集中增加伺服器的數量。

觸發器事件處理,將包含文字內容的檔案複製到指定的輸入資料夾。連續檔案由不同的伺服器進行處理。通過停止其中一臺伺服器可測試服務的可靠性。(請注意,由三個伺服器組成的叢集的法定數量規定,在任何時候都只能有一臺伺服器停機,以保持應用程式正常執行)。預設情況下,所包含的 log4j.properties 檔案啟用 TRACE 級的日誌記錄;請注意,伺服器拓撲結構將隨正在執行的伺服器更新。如果您讓領袖伺服器停機,那麼將選出新的領袖,並在那臺伺服器上啟用檔案輪詢流,從而保證進行連續的處理。

請參閱 參考資料 部分,瞭解有關使用 Apache ZooKeeper 和 Project Norbert 進行伺服器叢集感知的應用程式開發的更多資訊。