1. 程式人生 > >Java 執行時監控,第 3 部分: 監控應用程式生態系統的效能與可用性

Java 執行時監控,第 3 部分: 監控應用程式生態系統的效能與可用性

在本系列(共三篇文章)的 第 1 部分 和 第 2 部分 中,我介紹了監控 Java 應用程式的技巧和模式,在這兩部分中我把重點放在了 JVM 和應用程式類上。在這最後一期中,我將介紹從應用程式的依賴項(諸如底層作業系統、網路或者應用程式的後端資料庫)收集效能與可用性資料的技巧。在文章結尾我將論述管理收集資料的模式以及報告和視覺化資料的方法。

基於 Spring 的收集器

在 第 2 部分 中,我實現了一個用於管理監控服務的基本的基於 Spring 的元件模型。該模型的基本原理及益處有:

  • 使用基於 XML 的配置,使得管理大量用於配置更復雜效能資料收集器的引數集變得更加容易。
  • 採用關注點分離 的結構,這樣就可以使用更簡單的元件,這些元件之間的相互互動可以通過注入 Spring 的依賴項來實現。
  • Spring 給簡單的收集 bean 提供了一個生命週期,該週期由初始化啟動 和停止 操作組成,還提供了將 Java 管理擴充套件(Java Management Extension,JMX)管理介面公開給 bean 的選項,這樣就可以在執行時進行控制、監控和故障排除。

下面我將在本文的每個小節中介紹有關基於 Spring 的收集器的更多細節。

監控主機和作業系統

Java 應用程式總是運行於底層硬體和支援 JVM 的作業系統之上。一個全面的監控基礎設施中最關鍵的組成就是從硬體和 OS — 通常是通過 OS 收集 — 那裡收集效能、健康狀況和可用性指標的能力。本節就涵蓋了一些通過在 

第 1 部分 中介紹的 ITracer 類獲取這類資料並一直跟蹤到應用程式效能管理系統(application performance management,APM)的技巧。

典型的 OS 效能指標

下面這份摘要列出了典型指標,這些指標跨域作業系統的多個部分相關。雖然資料收集的細節迥異,而且資料的解釋也必須在給定的 OS 上下文中進行,但是這些指標在大多數標準主機上基本都是等效的:

  • CPU 使用:表示特定主機上的 CPU 的佔用情況。單位通常為百分比的使用率,在較低的級別將 CPU 忙碌時間表示為消逝的時鐘時間的某個特定時期的百分比。主機可以有多個 CPU,而 CPU 又可以包含多個核心,但多個核心通常都被 OS 抽象出來代表一個 CPU。例如,一個帶有兩個雙核 CPU 的主機會被說成有四個 CPU。指標通常可以按照每個 CPU 收集或者作為總資源利用率收集,後者表示所有處理器的總體使用情況。到底是要分別監控每一個 CPU 還是監控總體 CPU,通常要取決於軟體的本質及其內部架構。標準的多執行緒 Java 應用程式通常預設平衡所有 CPU 上的負載,所以監控總體較合適。但在某些情況下,個別 OS 程序是 “特定於” 特定 CPU 的,這時總體指標可能無法捕獲到適當級別的粒度。

    CPU 的使用通常被拆分成四個範疇:

    • 系統:執行系統的或者 OS 核心級的活動耗費的處理器時間
    • 使用者:執行使用者活動耗費的處理器時間
    • I/O 等待:處於空閒狀態等待完成某個 I/O 請求耗費的處理器時間
    • 空閒:暗指沒有進行任何處理器活動
    另外兩個相關指標為執行佇列長度(即等候 CPU 時間的請求的待處理事項)和上下文轉換(即將處理器時間分配從一個程序轉換到另一個程序的例項)。
  • 記憶體:最簡單的記憶體指標為可用或使用中的實體記憶體的百分比。其他需要考慮的有虛擬記憶體、記憶體分配率和重新分配率以及表明記憶體有哪些區域被使用的更細粒度的指標。
  • 磁碟與 I/O:磁碟指標為每一個邏輯或物理磁碟裝置的可用或使用中的磁碟空間的簡單(但是至關重要的)報告,還有這些裝置的讀取和寫入速率。
  • 網路:指網路介面上的資料傳輸速率和錯誤發生率,它通常被分為高階的網路協議範疇,如 TCP 和 IP。
  • 程序與程序組:可以說前面所述的指標都是特定主機的總活動。它們也可以劃分為相同的指標,但是代表個別程序或相關程序組的消耗或活動。監控程序對資源的使用情況有助於解釋主機上的每一個應用程式或者服務消耗的資源比例。有些應用程式只可以例項化一個程序;在其他情況下,一個諸如 Apache 2 Web Server 這樣的服務可以例項化代表一個邏輯服務的一群程序。

代理與無代理

不同的 OS 有著不同的效能資料獲取機制。我將呈現的收集資料的方式很多,但是在監控領域您可能經常要區別的是基於代理的 和無代理的 監控。也就是說在某些情況下,無需在目標主機上安裝其他特定的軟體也可以收集資料。但顯然監控通常都會涉及到某種代理,因為監控總是需要一個介面,資料要通過它來讀取。所以這裡真正區別的是是使用通常出現在給定 OS 中的代理 — 諸如 Linux® 伺服器上的 SSH — 還是安裝其他專用於監控和使收集的資料對外部收集器可用的軟體。兩種方法都涉及到如下的權衡標準:

  • 代理需要安裝其他的軟體並可能需要應用定期的維護補丁。在帶有大量主機的環境中,管理軟體工作不利於使用代理。
  • 如果代理實際上是與應用程式相同的程序的一部分的話,哪怕它是一個單獨的程序,代理程序的故障也將會矇蔽監控。雖然主機本身仍在執行且健康狀況良好,但是 APM 一定會因為無法到達代理而假定主機已停機。
  • 安裝在主機上的代理可能要比無代理遠端監控器的資料收集能力和事件監聽能力強得多。而且,報告總體指標可能需要收集好幾個原始底層指標,遠端收集這些指標的效率會很低。而本地的代理則能夠快速地收集資料,再合計資料,然後將合計的資料提供給遠端監控器。

歸根結底,最佳的解決方案可能就是既實現無代理的監控又實現基於代理的監控:本地代理負責收集大多數指標,而遠端監控器負責檢查諸如伺服器的執行情況和本地代理的狀態這樣的基本內容。

代理也可以有不同的選項。自治 代理按照自己的計劃收集資料,反之,響應 代理按請求遞送資料。此外,有些代理只將資料提供給請求程式,而有些則直接或間接地跟蹤資料一直到 APM 系統。

接下來我將呈現監控帶有 Linux 和 UNIX® OS 的主機的技巧。

監控 Linux 和 UNIX 主機

監控代理可以用來實現專門的本機庫以從 Linux 和 UNIX OS 收集效能資料。但是 Linux 和大多數 UNIX 變體都有很多內建資料收集工具,這些工具使得資料可以通過稱為 /proc 的虛擬檔案系統進行訪問。該檔案看起來像是普通檔案系統目錄裡面的普通文字檔案,但其實它們是常駐記憶體型資料結構,是通過文字檔案抽取的。由於這種資料可以很容易地通過大量標準命令列的實用工具或自定義的工具來讀取和解析,所以這些檔案較易於使用,而且它們的輸出既可以是通用的也可以是專用的。而且它們的效能也非常好,因為本質上它們是直接來源於記憶體的資料。

常見的用於從 /proc 中抽取效能資料的工具是 pssariostat 和 vmstat(參見 參考資料 查閱有關這些工具的參考文獻)。因此,一個有效地監控 Linux 和 UNIX 主機的方法就是執行 shell 命令並解析響應。類似的監控器可以用於很多種 Linux 和 UNIX 實現;雖然它們之間都有著些許差異,但是,使用一種可以完全重用收集過程的方式格式化資料是很簡單的。相反,專用的本機庫可能要根據每一個 Linux 和 UNIX 發行版而進行重編碼或重構(但它們正在讀取的 /proc 資料有可能相同)。而編寫專用於監控某一特定情況或可以標準化返回資料的格式這樣的自定義 shell 命令很容易,並且開銷較低。

現在我將展示幾種呼叫 shell 命令和跟蹤返回資料的方法。

Shell 命令的執行

要在一個 Linux 主機上執行資料收集監控,就一定要呼叫一個 shell。它可以是 bashcshksh 或其他任何允許呼叫目標指令碼或命令並可以檢索輸出的、受支援的 shell。最通常的選擇包括:

  • 本地 shell:如果目標主機上執行著 JVM 的話,那麼執行緒可以通過呼叫 java.lang.Process 來訪問這種 shell。
  • 遠端 Telnet 或 rsh:這兩個服務都允許呼叫 shell 和 shell 命令,但由於它們的安全性相對較低,所以很少使用它們。它們在大多數現代發行版上的預設狀態為禁用。
  • 安全 Shell(SSH):SSH 是最為常用的遠端 shell。它提供了對 Linux shell 的完全訪問,而且被公認是安全的。在文中基於 Shell 的例子裡,我將主要使用該機制。大多數 OS 都提供 SSH 服務,包括所有 UNIX 系列、Microsoft® Windows®、OS/400 及 z/OS。

圖 1 展示了本地 shell 與遠端 shell 的基本差異:

圖 1. 本地 shell 與遠端 shell
本地 shell 與遠端 shell

要用伺服器啟動一個無人值守的對話需要進行一些設定。首先必須要建立一個由私鑰和公鑰組成的 SSH 密匙對。然後將公鑰置於目標伺服器,私鑰置於遠端監控伺服器 —— 資料收集器可以在此獲取該私鑰。完成上述操作之後,資料收集器便能夠提供私鑰及其密碼短語(passphrase),並能夠訪問目標伺服器上的安全遠端 shell 了。使用了密匙對之後,目標帳戶的密碼就是多餘的了,根本不需要它。具體設定步驟如下:

  1. 確保目標主機在本地的已知主機的檔案中有入口。這個檔案列出了已知 IP 地址或名稱以及為每一個已知 IP 地址或名稱驗證的相關 SSH 公鑰。在使用者級別,該檔案通常為使用者主目錄中的 ~/.ssh/known_hosts 檔案。
  2. 用監控帳戶(例如,monitoruser)連線到目標伺服器。
  3. 在主目錄中建立一個名為 .ssh 的子目錄。
  4. 將目錄改為 e .ssh 目錄併發布 ssh-keygen -t dsa 命令。該命令提示金鑰名和密碼短語。然後會生成兩個叫做 monitoruser_dsa(私鑰)和 monitoruser._dsa.pub(公鑰)的檔案。
  5. 將私鑰複製到一個安全的可訪問的位置,資料收集器將從這個位置執行。
  6. 用 cat monitoruser_dsa.pub >> authorized_keys 命令將私鑰內容追加到 .ssh 目錄中名為 authorized_keys 的檔案中。

清單 1 展示了我剛才所描述的過程:

清單 1. 建立一個 SSH 密匙對
[email protected]:~$ mkdir .ssh
[email protected]:~$ cd .ssh
[email protected]:~/.ssh$ ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/home/whitehen/.ssh/id_dsa): whitehen_dsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in whitehen_dsa.
Your public key has been saved in whitehen_dsa.pub.
The key fingerprint is:
46:cd:d4:e4:b1:28:d0:41:f3:ea:3b:8a:74:cb:57:e5 [email protected]
[email protected]:~/.ssh$ cat whitehen_dsa.pub >> authorized_keys
[email protected]:~/.ssh$

現在資料收集器已經能夠使用 SSH 連線到目標 Linux 主機,該 SSH 連線名為 whitehen-desktop,它執行著 Ubuntu Linux。

這個例子的資料收集器將使用一個名為 org.runtimemonitoring.spring.collectors.shell.ShellCollector 的通用收集器類來實現。該類的一個例項將以 UbuntuDesktopRemoteShellCollector 這個名稱部署在一個 Spring 上下文中。但要完成整個過程還需要一些其他的依賴項:

  • 需要有一個排程器來每分鐘呼叫一次收集器。該排程器由 java.util.concurrent.ScheduledThreadPoolExeutor 的一個例項來實現,它既可以提供一個有計劃的回撥機制,又可以提供一個執行緒池。這個排程器將以 CollectionScheduler 這個名稱部署於 Spring。
  • SSH shell 實現對伺服器呼叫命令並返回結果。這個可以通過 org.runtimemonitoring.spring.collectors.shell.ssh.JSchRemoteShell的一個例項來實現。這個類是一個名為 org.runtimemonitoring.spring.collectors.shell.IRemoteShell 的 Shell 介面的實現,它將以UbuntuDesktopRemoteShell 這個名稱部署於 Spring。
  • 該收集器不會硬編碼一組命令及其相關解析例程,而是使用 org.runtimemonitoring.spring.collectors.shell.commands.CommandSet的一個例項,它將以 UbuntuDesktopCommandSet 這個名稱部署於 Spring 中。命令集從一個 XML 文件載入,該文件表述了:
    • 將要用來執行 shell 的目標平臺
    • 將要執行的命令
    • 將如何解析返回資料並將其對映到 APM 跟蹤名稱空間
    稍候我將提供有關這些定義的更多細節。圖 2 大致解釋了收集器、shell 和命令集三者之間的關係:
圖 2. 收集器、shell 和命令集
收集器、shell 和命令集

下面我將專門介紹一些關於專用於生成效能資料的命令以及它們的配置方法的簡短示例。一個經典的例子就是 sar 命令。Linux 手冊(參見 考資料)對 sar 的定義是收集、報告或者儲存系統活動資訊。該命令非常靈活,它有超過 20 個引數,這些引數可以結合起來使用。一個簡單的選擇就是呼叫 sar -u 1 3,它報告了在三個時間間隔內(一個時間間隔為一秒)度量的 CPU 使用。清單 2 展示了它的輸出:

清單 2. sar 命令的輸出
[email protected]:~$ sar -u 1 3
Linux 2.6.22-14-generic (whitehen-desktop)      06/02/2008

06:53:24 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
06:53:25 PM     all      0.00      0.00      0.00      0.00      0.00    100.00
06:53:26 PM     all      0.00     35.71      0.00      0.00      0.00     64.29
06:53:27 PM     all      0.00     20.79      0.99      0.00      0.00     78.22
Average:        all      0.00     18.73      0.33      0.00      0.00     80.94

該輸出可以劃分成開頭、標題、三個時間間隔的資料讀數和一個讀數彙總平均值。這裡的目標是要執行該 shell 命令、捕獲輸出,然後解析輸出並跟蹤到 APM 系統。輸出資料的格式是夠簡單的,但卻可能根據具體的版本而不同(輕微或顯著的不同),而且其他 sar 選項也會返回完全不同的資料(更不用說其他的命令了,它們當然會返回不同的資料格式)。例如,清單 3 展示了一個顯示活動的 socket 行為的 sar 執行:

清單 3. 顯示 socket 行為的 sar
[email protected]:~$ sar -n SOCK 1
Linux 2.6.22-14-generic (whitehen-desktop)      06/02/2008

06:55:10 PM    totsck    tcpsck    udpsck    rawsck   ip-frag
06:55:11 PM       453         7         9         0         0
Average:          453         7         9         0         0

因此,現在所需要的是一個解決方案:怎樣在不重新編碼收集器的情況下快速配置不同的資料。還可以將諸如 totsck 這樣的含糊詞語翻譯成像 Total Used Sockets 這樣的更易讀的短語,以免收集到的記錄會干擾 APM 系統。

在某些情況下,您可以選擇以 XML 格式獲取這個資料。例如,SysStat 包(參見 參考資料)中的 sadf 命令會以 XML 格式生成很多被經常收集的 Linux 監控資料。XML 格式增加了資料的可預測性和結構,並真正排除了分析資料、將資料對映到跟蹤名稱空間和解碼模糊詞語的需要。然而,這些工具對於您想監控的可以訪問 shell 的系統可能是不可用的,因此需要一種靈活的文字解析和對映解決方案。

承接上面兩個關於 sar 的應用的例子,接下來我將呈現一個設定 Spring bean 定義以監控這些資料的例子。所有引用的例子都包含在本文的示例程式碼中(參見 下載)。

首先,SpringCollector 實現的主要入口點為 org.runtimemonitoring.spring.collectors.SpringCollector。它採用了一個引數:Spring bean 配置檔案所在的目錄的名稱。SpringCollector 載入了任何帶有 .xml 副檔名的檔案,並將他們當作 bean 描述符。該目錄為位於專案根目錄中的 ./spring-collectors 目錄(稍後我將在本文中概述此目錄中的所有檔案。有多個檔案可以選擇,而且可以將所有的定義捆綁成一個,但要用虛構的函式單獨隔開,以保持一定的順序)。這個例子中的三個 bean 定義代表 shell 收集器、shell 和命令集。清單 4 展示了它們的描述符:

清單 4. shell 收集器、shell 與命令集的 Bean 描述符
<!-- The Collector -->
<bean id="UbuntuDesktopRemoteShellCollector"
  class="org.runtimemonitoring.spring.collectors.shell.ShellCollector"
  init-method="springStart">
  <property name="shell" ref="UbuntuDesktopRemoteShell"/>
  <property name="commandSet" ref="UbuntuDesktopCommandSet"/>
  <property name="scheduler" ref="CollectionScheduler"/>
  <property name="tracingNameSpace" value="Hosts,Linux,whitehen-desktop"/>
  <property name="frequency" value="5000"/>
</bean>

<!-- The Shell -->
<bean id="UbuntuDesktopRemoteShell"
   class="org.runtimemonitoring.spring.collectors.shell.ssh.JSchRemoteShell"
   init-method="init"
   destroy-method="close">
   <property name="userName" value="whitehen"/>
   <property name="hostName" value="whitehen-desktop"/>
   <property name="port" value="22"/>
   <property name="knownHostsFile"
      value="C:/Documents and Settings/whitehen/.ssh/known_hosts"/>
   <property name="privateKey"
      value="C:/keys/whitehen/ubuntu-desktop/whitehen_dsa"/>
   <property name="passphrase" value="Hello World"/>
</bean>

<!-- The CommandSet -->
<bean id="UbuntuDesktopCommandSet"
   class="org.runtimemonitoring.spring.collectors.shell.commands.CommandSet">
   <constructor-arg type="java.net.URL"
      value="file:///C:/projects//RuntimeMonitoring/commands/xml/UbuntuDesktop.xml"/>
</bean>

清單 4 中的 CommandSet 只有一個 idUbuntuDesktopCommandSet)和另一個 XML 檔案的 URL。這是因為命令集太大,我不想因為它們而使 Spring 檔案顯得很混亂。稍後我將描述 CommandSet

清單 3 中的第一個 bean 為 UbuntuDesktopRemoteShellCollector。它的 bean id 值可以是任意的描述性的值,但是當從另一個 bean 引用該 bean 時需要保持一致。這個例子中的類為 org.runtimemonitoring.spring.collectors.shell.ShellCollector,它是一個通過類似於 Shell 的介面來收集資料的通用類。其他重要屬性有:

  • shell:收集器用來從 shell 命令呼叫和檢索資料的 shell 類的例項。Spring 用 UbuntuDesktopCommandSet 的 bean id 來注入該 Shell 的例項。
  • commandSet:代表一組命令和相關解析、跟蹤名稱空間對映指令的 CommandSet 例項。Spring 用 UbuntuDesktopRemoteShell 的 bean id注入該命令集的示例。
  • scheduler:一個排程執行緒池的引用,該執行緒池管理資料收集的排程,將這項工作具體分配給一個執行緒來完成。
  • tracingNameSpace:跟蹤名稱空間的字首,它控制著這些指標將被跟蹤到 APM 樹中的哪個位置。
  • frequency:資料收集頻率,以毫秒為單位。

清單 4 中的第二個 bean 為 shell,它是一個名為 org.runtimemonitoring.spring.collectors.shell.ssh.JSchRemoteShell 的 SSH shell 的實現。該類使用從 JCraft.com(參見 參考資料)下載的 JSch 來實現。它的其他重要屬性有:

  • userName:使用者用來連線到 Linux 伺服器的名稱
  • hostName:連線到的 Linux 伺服器的名稱(或 IP 地址)。
  • port:Linux 伺服器埠,sshd 在這個埠上監聽。
  • knownHostFile:一個包含主機名稱和 SSH 伺服器的 SSH 證書的檔案,該 SSH 伺服器對於執行 SSH 客戶機的本地主機是 “已知的”(有趣的是,SSH 中的這個安全機制恰好顛倒了傳統的安全結構,使用這種機制,除非主機是 “已知的” 並可以給出匹配的證書,否則客戶機不會信任主機,並拒絕連線)。
  • privateKey:用來驗證 SSH 伺服器的 SSH 私鑰檔案。
  • passPhrase:用來解鎖私鑰的密碼短語。它的外表與密碼類似,只是它沒有被傳送到伺服器,而且它只用於本地解密私鑰。

清單 5 展示了 CommandSet 的內部細節:

清單 5. CommandSet 內部細節
<CommandSet name="UbuntuDesktop">
   <Commands>
      <Command>
         <shellCommand>sar -u 1</shellCommand>
         <paragraphSplitter>\n\n</paragraphSplitter>
    <Extractors>
       <CommandResultExtract>
          <paragraph id="1" name="CPU Utilization"/>
          <columns entryName="1" values="2-7" offset="1">
             <remove>PM</remove>
          </columns>
          <tracers default="SINT"/>
               <filterLine>Average:.*</filterLine>
          <lineSplit>\n</lineSplit>
       </CommandResultExtract>
    </Extractors>
      </Command>
      <Command>
         <shellCommand>sar -n SOCK 1</shellCommand>
    <paragraphSplitter>\n\n</paragraphSplitter>
    <Extractors>
       <CommandResultExtract>
          <paragraph id="1" name="Socket Activity"/>
          <columns values="1-5" offset="1">
             <remove>PM</remove>
             <namemapping from="ip-frag" to="IP Fragments"/>
             <namemapping from="rawsck" to="Raw Sockets"/>
             <namemapping from="tcpsck" to="TCP Sockets"/>
             <namemapping from="totsck" to="Total Sockets"/>
             <namemapping from="udpsck" to="UDP Sockets"/>
          </columns>
          <tracers default="SINT"/>
               <filterLine>Average:.*</filterLine>
          <lineSplit>\n</lineSplit>
       </CommandResultExtract>
    </Extractors>
      </Command>
   </Commands>
</CommandSet>