集羣感知

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

分佈式鎖和共享數據:如果有需要,分佈式應用程序應該能夠訪問的特性包括分佈式鎖和共享數據結構,如隊列和映射。

[size=0.76em]示例:Spring Integration

[size=0.76em]我們的代表示例是一個企業應用程序集成 (EAI) 場景,我們將利用基於 Spring Integration 的模擬應用程序處理該場景。該應用程序具有以下特徵和要求:

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

[size=0.76em]圖 1 顯示了集成事件和從模擬源應用程序出站的消息處理流。


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

[size=0.76em]設計解決方案的架構

[size=0.76em]要爲該用例開發一個解決方案,第一步是分發要在一個服務器集羣上運行的集成應用程序。這應該能增加處理吞吐量,並能確保高可用性和可擴展性。單次進程的失敗不會中止整個應用程序。

[size=0.76em]一旦完成分配,集成應用程序將從應用程序的數據存儲中獲得集成事件。服務器集羣中的單臺服務器將通過一個合適的應用程序適配器從事件存儲獲取應用程序事件,然後將這些事件分發給集羣中的其餘服務器進行處理。這個單臺服務器擔任領袖服務器,或任務協調者 的角色,負責將事件(處理任務)分發給整個集羣中的其餘服務器。

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

[size=0.76em]圖 2 顯示了用例解決方案的示意圖和組件,我們會在下一節中進一步描述它們。


圖 2. 用例解決方案的示意圖和服務器集羣組件

[size=0.76em]服務器集羣

[size=0.76em]我們的集成應用程序需要服務器組相關的特性,但是無論是 Java 標準版 (Java SE) 還是 Java 企業版 (Java EE) 均沒有現成提供這些特性。這些示例包括服務器集羣和動態服務器領袖選舉。

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


圖 3. 服務器集羣的技術映射

[size=0.76em]關於模擬的應用程序

[size=0.76em]模擬應用程序的目的是演示如何使用 Apache ZooKeeper 和 Project Norbert 來解決在開發基於 Java 的服務器集羣中所面臨的常見挑戰。該應用程序的工作原理如下:

  • 應用程序事件存儲由一個共享文件夾來模擬,集成服務器集羣內的所有服務器都可以訪問該文件夾。
  • 使用保存在此共享文件夾上的文件(以及其數據)來模擬集成事件。也可以使用外部腳本來不斷創建文件,從而模擬事件的創建。
  • 基於 Spring Integration 的文件輪詢組件(入站事件適配器)從應用程序事件存儲獲取事件,這是由共享文件系統文件夾模擬的事件。然後,文件數據被分發到其餘的服務器集羣成員進行處理。
  • 事件處理的模擬方法是:用簡短的標頭類型信息(比如 server id 和 timestamp)作爲文件數據的前綴。
  • 合作伙伴應用程序由其他一些獨特的共享文件系統模擬文件夾,每個合作伙伴應用程序對應一個文件夾。

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

[size=0.76em]Apache ZooKeeper 和 Project Norbert

[size=0.76em]首先由 Yahoo Research 開發的 ZooKeeper 最初被 Apache Software Foundation 採納爲 Hadoop 的一個子項目。如今,它已是一個頂級項目,可提供分佈式組協調服務。我們將使用 ZooKeeper 創建服務器集羣來託管我們的模擬應用程序。ZooKeeper 也將實現應用程序所需要的服務器領袖選舉功能。(領袖選舉對於 ZooKeeper 提供的所有其他組協調功能是必不可少的。)

[size=0.76em]ZooKeeper 服務器通過 znode(ZooKeeper 數據節點)實現服務器協調,znode 是在內存中複製的類似於分層文件系統的數據模型。和文件一樣,znode 可以保存數據,但它們也像目錄一樣,可以包含子級 znode。

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

[size=0.76em]關於 ZooKeeper 的更多信息:

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

[size=0.76em]利用 Project Norbert 實現組管理

[size=0.76em]LinkedIn 的 Project Norbert 掛接到一個基於 Apache ZooKeeper 的集羣,以提供具有服務器集羣成員感知的應用程序。Norbert 在運行時動態完成該操作。Norbert 也封裝了一個 JBoss Netty 服務器,並提供了相應的客戶端模塊來支持應用程序交換消息。需要注意的是,早期版本的 Norbert 需要使用 Google Protocol Buffers 對象序列化消息庫。目前的版本支持自定義對象的序列化。(請參閱 參考資料 瞭解更多信息。)

需要注意的是,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 上訪問相應的類,其中包括解決方案代碼,具體參見 參考資料)鏈接。
還要注意的是,消息傳送的應用程序回調需要一個線程池。


原地址:http://www.itpub.net/thread-1762281-1-1.html

發佈了21 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章