王子鵬,張樹東,任仲山,胡建亞
(1.首都師范大學(xué)信息工程學(xué)院,北京 100048;2.北京市成像技術(shù)高精尖創(chuàng)新中心,北京 100000;3.中國科學(xué)院軟件研究所,北京 100223)
插樁是一種獲取軟件狀態(tài)的方法,是軟件性能管理工具的核心部分,常常使用于對大規(guī)模分布式系統(tǒng)的監(jiān)控和跟蹤[1],一般實現(xiàn)方法是程序員對被監(jiān)控軟件系統(tǒng)進(jìn)行代碼指令注入,這些被注入的代碼可以實現(xiàn)各種自定義功能,例如:記錄功能函數(shù)的執(zhí)行時間、調(diào)用序列、植入回調(diào)函數(shù)、收集所需的信息并將數(shù)據(jù)記錄到數(shù)據(jù)庫中等?,F(xiàn)如今大多數(shù)的插樁工具使用的是動態(tài)二進(jìn)制插樁技術(shù),例如DTrace[2]、Pin[3]等工具,但這些工具通常只提供插樁本身的實現(xiàn),并不會提供選擇插樁點的和插樁粒度的工作,這就導(dǎo)致當(dāng)一些性能管理工具使用插樁技術(shù)時難以選擇適宜的插樁點和控制插樁粒度。傳統(tǒng)的性能管理工具選擇全插樁的策略,即將插樁點植入到被監(jiān)控軟件系統(tǒng) (下文稱作目標(biāo)程序)所有運(yùn)行的類中,這就導(dǎo)致了大量的插樁點產(chǎn)生了極大系統(tǒng)資源消耗,并且插樁點在目標(biāo)程序運(yùn)行的過程中是完全不會改變的[4-5]。在對目標(biāo)程序的實際監(jiān)控中,并不是所有的類都需要插樁,如果對一個目標(biāo)程序的插樁粒度過粗,將可能導(dǎo)致達(dá)不到用戶對目標(biāo)程序性能管理的要求,例如難以定位軟件發(fā)生異常的具體位置。如果對一個目標(biāo)程序的插樁粒度過細(xì),將產(chǎn)生大量的資源消耗,甚至影響到目標(biāo)程序本身的運(yùn)行。所以一個根據(jù)實際需要,動態(tài)自適應(yīng)地改變插樁粒度的插樁框架不僅能夠滿足用戶的需求,而且能夠把資源消耗減少到最小,是十分必要的[6-7]。針對此問題,本文提出了一種全新的自適應(yīng)插樁框架。
本文針對Java應(yīng)用程序,提供一個基于機(jī)器學(xué)習(xí)的自適應(yīng)插樁架構(gòu)[8],可以針對軟件性能管理工具進(jìn)行適當(dāng)?shù)牟鍢豆ぷ?本文可以識別不同來源的Java類,將插樁點植入到用戶關(guān)心的類中。本文還提出插樁粒度矯正機(jī)制[9],在被測程序運(yùn)行時對插樁粒度進(jìn)行動態(tài)控制,控制整個系統(tǒng)插樁資源的消耗。
本文的插樁架構(gòu)是為性能管理軟件服務(wù)的組件,它可以在極小的資源消耗的情況的下進(jìn)行插樁,得到跟蹤數(shù)據(jù)。本文的主要目標(biāo)有以下兩點:1)對不同來源的Java類進(jìn)行識別,將插樁點植入到用戶編寫類中。2)在被測程序運(yùn)行中動態(tài)的對插樁粒度進(jìn)行矯正。接下來,通過一個普通的例子,對問題進(jìn)行建模。有一個簡單的Java編寫的Web客戶端Client,當(dāng)此客戶端運(yùn)行的時候,性能管理工具就需要獲取此程序的跟蹤信息,此時就需要插樁工具對此程序進(jìn)行插樁。為了方便表述,這里將相關(guān)的概念和術(shù)語進(jìn)行引入和說明。
C={c1,c2,…,cn}表示插樁類,即在被測程序中,要將跟蹤代碼注入到哪些類當(dāng)中。
M={m1,m2,…,mn}表示插樁方法,即是在c中,將跟蹤代碼注入到哪些方法中。
p表示插樁點,即是在m中,將跟蹤代碼注入的具體位置。每兩個p,對應(yīng)一個集合Sn={ps,pe},S是插樁點的作用域,表示這個插樁點的作用范圍,ps為插樁起始位置,pe為插樁結(jié)束位置。一個Sn也可以看作一個插樁探針,S={s1,s2,…,sn}。
I={i1,i2,…,in}表示插樁的內(nèi)容,即是在插樁點上,被注入的具體代碼。
C中每個cn包括一個集合m,每個mn包括一個集合s,每個sn包括一個集合i。整個插樁信息的關(guān)系呈現(xiàn)出一個樹狀的關(guān)系,如圖1所示。
當(dāng)被測程序運(yùn)行時,插樁要進(jìn)行如下步驟:
1)確定被測程序中所有的插樁類集合C;
2)篩選并確定cn插樁方法的集合M;
3)篩選并確定mn中所有的插樁點作用域集合S;
4)確定插樁點sn的中的ps和pe;
5)確定每個插樁點要注入的代碼集合I;
6)進(jìn)行插樁。
當(dāng)程序運(yùn)行時,有:
其中類c1為用戶自定義的類,c2,c3,c4,c5,c6屬于Java提供的類庫中的類。在傳統(tǒng)的插樁方法中,不會區(qū)分用戶自定義的類和非用戶自定義類,選擇全插樁的策略,即對所有類進(jìn)行分析和插樁。但是,全插樁的策略會造成極大的資源消耗和浪費,而且用戶在目標(biāo)程序?qū)嶋H運(yùn)行中,不關(guān)心其他類運(yùn)行的情況。所以為了解決此問題,本文提出了一種基于樸素貝葉斯算法的分類模型,可以區(qū)分不同來源的Java類,將來自用戶編寫的類,JDK中的類,第三方開發(fā)的工具類區(qū)分開。除此而外,本文能夠在日常對目標(biāo)程序的插樁過程中,依照目標(biāo)程序的性能變化,自動地改變插樁的粒度,減少插樁的資源消耗。
圖1 插樁的樹形關(guān)系圖
在本章里,本文將主要介紹本文的體系結(jié)構(gòu)以及進(jìn)行插樁的過程。
圖2顯示了本文的高層體系結(jié)構(gòu)以及插樁過程。如圖所示,自適應(yīng)插樁體系中,主要有4個部分,分別是目標(biāo)程序,Java虛擬機(jī),Agent以及客戶端。
目標(biāo)程序:指的是要被插樁的Java程序。
Java虛擬機(jī):Java程序運(yùn)行在Java虛擬機(jī)上,Agent在插樁的過程中不直接與目標(biāo)程序進(jìn)行交互,而通過Java虛擬機(jī)完成對目標(biāo)程序信息的獲取以及對目標(biāo)程序進(jìn)行插樁。這樣可以使得插樁對目標(biāo)程序本身造成較小的干擾,保證了目標(biāo)程序本身的獨立性和安全性。在圖中,JVMToolInterface是Java虛擬機(jī)提供的一個工具接口,它是在JavaSE5提出的一個工具。JVMToolInterface提供接口可以獲取Java虛擬機(jī)的狀態(tài)以及運(yùn)行在Java虛擬機(jī)上的程序的狀態(tài)信息,也可以控制這些程序的執(zhí)行,具有廣泛的用途。本文則通過JVMToolInterface來獲取目標(biāo)程序的信息。JavaInstrumentation是JavaSE6的一個新特性,它最大的特點在于可以在目標(biāo)程序運(yùn)行時動態(tài)的將目標(biāo)程序的類進(jìn)行修改,本文則利用它的這一特點,對目標(biāo)程序進(jìn)行動態(tài)二進(jìn)制插樁。
Agent:Agent是整個插樁體系結(jié)構(gòu)中最核心的部分,絕大部分的功能都在 Agent中實現(xiàn),主要包括了以下幾個部分:
裝載器:裝載器主要與Java虛擬機(jī)進(jìn)行交互,它的主要功能是提取運(yùn)行在Java虛擬機(jī)中的程序的信息,主要是提取目標(biāo)程序要運(yùn)行所需的全部類,并將這些信息寫入目標(biāo)程序類表。
分類器:分類器的作用就是將不同來源的三種Java類區(qū)分開,對用戶關(guān)心的類進(jìn)行插樁,這種方式可以極大地減少資源的消耗。分類器初步的確定了插樁類集合C,分類器的具體實現(xiàn)將在第三章進(jìn)行詳細(xì)說明。分類器將判定為用戶編寫的類的寫入用戶類表中。
分析器:分析器的主要功能對分類器初步確定的插樁類集合C進(jìn)行分析,得出每個類的插樁方法集合M,作用域集合S,插樁點集合P,插樁內(nèi)容集合I。
插樁表:插樁表是本文Agent的核心數(shù)據(jù)表,它保存了在目標(biāo)程序中所有的插樁點。除了分析器對它進(jìn)行寫入外,日常由自適應(yīng)器對它進(jìn)行操作。執(zhí)行器會依照插樁表中的信息去實施插樁。
執(zhí)行器:執(zhí)行器的主要功能是依照插樁表中的內(nèi)容進(jìn)行實際的插樁工作。執(zhí)行器是利用Javassist類庫完成的[10]。它提供了對Java程序字節(jié)碼進(jìn)行操作和修改的方法,可以在Java程序運(yùn)行的過程中對程序類進(jìn)行修改,優(yōu)點在于簡單和快速。本文執(zhí)行器使用Javassist類庫,通過JavaInstrumentation接口對目標(biāo)程序字節(jié)碼進(jìn)行修改,達(dá)到探針注入的目的。
數(shù)據(jù)回收器:數(shù)據(jù)回收器的主要工作就是回收目標(biāo)程序的性能數(shù)據(jù)信息,數(shù)據(jù)回收器會依照一定的采樣策略去回收數(shù)據(jù)。
自適應(yīng)器:自適應(yīng)器是本文Agent的核心部件,它的主要工作是動態(tài)地調(diào)整插樁粒度,達(dá)到減少資源負(fù)載目的。自適應(yīng)器的工作時期是在插樁程序日常運(yùn)行的階段。自適應(yīng)器是根據(jù)目標(biāo)程序的性能數(shù)據(jù)反饋,利用基于機(jī)器學(xué)習(xí)的算法處理和分析性能數(shù)據(jù)信息,找出異常的程序執(zhí)行路徑,對其進(jìn)行更細(xì)粒度的插樁,定位異常根源,具體的實現(xiàn)將在第四章中詳細(xì)說明。
客戶端:客戶端的主要功能是向用戶展示目標(biāo)程序的性能數(shù)據(jù)。
本文將Agent對目標(biāo)程序的插樁過程分為兩個主要階段:1)初始化階段,2)自適應(yīng)階段。如圖2所示,初始化階段用點虛線表示,自適應(yīng)階段用實線表示,初始化與自適應(yīng)都要進(jìn)行的共同階段用短劃線表示。其中,初始化階段的執(zhí)行步驟用圓括號表示,例如:[2],自適應(yīng)階段的執(zhí)行步驟用尖括號表示,例如:<1>。
1)初始化階段。
圖2 自適應(yīng)插樁的高層體系結(jié)構(gòu)以及插樁過程
初始化階段主要指的是當(dāng)本文Agent第一次插樁一個目標(biāo)程序的某個類所要經(jīng)歷的階段,本文將這個過程稱為初始化過程。當(dāng)一個Java程序運(yùn)行時,如果它的源代碼是Java格式的文件,它的源代碼將會編譯成為.class格式的文件并加載在Java虛擬機(jī)上運(yùn)行,如果是.class格式的文件,將直接加載在Java虛擬機(jī)上運(yùn)行 (步驟 [1]),這是所有Java程序運(yùn)行的必經(jīng)階段。當(dāng)目標(biāo)程序運(yùn)行之后,本文Agent進(jìn)入初始化階段,本文 Agent的裝載器通過 JVMToolInterface將目標(biāo)程序的所有類信息獲取 (步驟 [2]),并存入目標(biāo)程序類表 (步驟 [3])。當(dāng)Agent獲取到目標(biāo)程序類之后,分類器開始工作,從目標(biāo)程序類表中讀出這些數(shù)據(jù)并進(jìn)行分析 (步驟 [4])),分類器將利用基于機(jī)器學(xué)習(xí)的算法將這些類分類,并找出其中屬于用戶自定義編寫的類,并把這些類的信息寫入用戶類表中 (步驟[5])。然后分析器讀出用戶類表中的用戶類信息 (步驟[6])。此后,分析器對這些類進(jìn)行分析,得出具體的插樁信息,這些信息包括插樁類集合C,插樁方法集合M,插樁探針集合S,插樁點集合P,以及插樁內(nèi)容集合I。并將這些信息數(shù)據(jù)寫入插樁表中 (步驟 [7])。當(dāng)?shù)玫讲鍢缎畔⒑?(步驟[8]),執(zhí)行器開始工作,將通過JavaInstrumentationInterface把信息交給Java虛擬機(jī) (步驟 [9]),此時將重定義目標(biāo)程序的類,如圖2所示為步驟[10],探針被注入進(jìn)目標(biāo)程序。當(dāng)目標(biāo)程序進(jìn)入到特定的階段后,探針將返回數(shù)據(jù)到數(shù)據(jù)回收器 (步驟[11]),數(shù)據(jù)回收器通過一定的插樁策略進(jìn)行采樣,將目標(biāo)程序的性能數(shù)據(jù)寫入到回收數(shù)據(jù)庫中 (步驟 [12])。此時則插樁的初始化階段完成。
2)自適應(yīng)階段。
在完成初次插樁之后,為了更快、更準(zhǔn)確、更高效地發(fā)現(xiàn)應(yīng)用中潛在的問題,需要根據(jù)監(jiān)測數(shù)據(jù)對插樁的粒度進(jìn)行動態(tài)調(diào)整,這是因為當(dāng)對一個目標(biāo)程序的插樁粒度過粗,將導(dǎo)致無法準(zhǔn)確定位發(fā)生異常的具體位置;如果對一個目標(biāo)程序的插樁粒度過細(xì),會導(dǎo)致應(yīng)用性能嚴(yán)重下降,同時會帶來大量的資源消耗。所以本文在目標(biāo)程序正常啟動運(yùn)行后會進(jìn)入自適應(yīng)調(diào)整階段,對插樁粒度進(jìn)行動態(tài)的調(diào)整,達(dá)到滿足性能管理需求的同時將插樁資源消耗減少的最小。
當(dāng)Agent完成初始化階段之后,當(dāng)執(zhí)行初始化階段的插樁函數(shù)時,將能夠收集到目標(biāo)程序的性能跟蹤數(shù)據(jù)。此時,自適應(yīng)器將請求讀取并得到這些數(shù)據(jù),如圖2所示為步驟<1>,步驟<2>。自適應(yīng)器將會對這些性能跟蹤數(shù)據(jù)進(jìn)行分析,通過基于機(jī)器學(xué)習(xí)的算法,會得出一個正常性能達(dá)標(biāo)范圍,當(dāng)一段目標(biāo)程序的跟蹤路徑的性能數(shù)據(jù)不在這個范圍之內(nèi)時,就說明這個跟蹤路徑可能出現(xiàn)了性能問題,此時自適應(yīng)器會將此跟蹤路徑的插樁粒度變細(xì),增加更多的插樁點。相反,對于一段執(zhí)行路徑的性能在設(shè)定時間內(nèi)性能滿足要求或當(dāng)某些有問題的跟蹤路徑性能恢復(fù)正常,自適應(yīng)器會通過去除一些插樁點的方法將該路徑的插樁粒度變粗。自適應(yīng)器將通過修改插樁表完成這些變更,如圖2所示為步驟<4>,并通知執(zhí)行器插樁信息發(fā)生了改變,如圖2所示為步驟<5>,此后,執(zhí)行器會執(zhí)行調(diào)整后的插樁表,和初始化階段中的步驟相同,為步驟<6>,步驟<7>,步驟<8>,步驟<9>,步驟<10>。除此而外,用戶也可以通過客戶端手動修改插樁粒度。
本文通過在應(yīng)用執(zhí)行中自適應(yīng)調(diào)整插樁的粒度,實現(xiàn)了在最小的插樁資源消耗下,滿足用戶對目標(biāo)程序的性能管理的需求。
本文的插樁架構(gòu)是針對Java程序,在本章內(nèi)將詳細(xì)介紹本文核心功能部分的實現(xiàn),依次將介紹1)基于樸素貝葉斯算法的分類器的實現(xiàn)[11]。2)插片式分析器的實現(xiàn)。3)基于機(jī)器學(xué)習(xí)組合算法的性能自適應(yīng)器的實現(xiàn)[12-13]。
對于Java程序來說,其代碼一般來自3個部分。開發(fā)人員自定義編寫的代碼,調(diào)用JDK類中的代碼以及借助第三方提供的庫。它們有各自的類,在Java程序運(yùn)行時共同作用,形成完整的Java程序。但是,對于性能管理工具來說,在程序的實際運(yùn)行中,用戶通常只關(guān)心他們自己編寫的類的性能表現(xiàn),而傳統(tǒng)的性能管理插樁策略是對所有的類進(jìn)行插樁,這樣就造成了大量的資源消耗。所以,在插樁前,將用戶自定義編寫的類,系統(tǒng)類庫中的類以及第三方類庫中的類區(qū)分開,只插樁用戶自定義的類,這樣可以很大的減少插樁資源負(fù)載,增大插樁工具的實用性,是十分有必要的。
3.1.1 采集數(shù)據(jù)
對于本文的類分類器來說,數(shù)據(jù)就是一個個類的信息,為了使數(shù)據(jù)能夠有足夠的代表性和得到廣泛的認(rèn)可,本文完成了對常用的Java開源類庫中類信息數(shù)據(jù)的采集,分別對自定義類、系統(tǒng)類庫、第三方插件庫中的類進(jìn)行了采集;包括第三方插件庫 Maven、Solr、Commons Math、Common-Configuration等在內(nèi),一共收集到77 000多個類的信息。
3.1.2 特征選擇
本文的目的是為了區(qū)分不同來源的Java類,為此將盡可能選取有明顯區(qū)分意義的特征。
1)對于一個Java類來說,程序通過類名對其進(jìn)行調(diào)用,類名也是對不同的類進(jìn)行區(qū)分的最直接的標(biāo)志,所以選取類名為第一個特征。
2)在Java程序的運(yùn)行機(jī)制中,源代碼被編譯為.class格式的文件進(jìn)行加載使用,每一個.class文件通常代表一個類,當(dāng)程序需要使用某個類時,Java由類加載器來加載此類。在Java的運(yùn)行機(jī)制中,有雙親委派模型,由三種類加載器來加載不同類型的類,分別是啟動類加載器,擴(kuò)展類加載器,應(yīng)用程序類加載器。啟動類加載器加載路徑<JAVA_HOME>/lib中的類,擴(kuò)展類加載器加載路徑<JAVA_HOME>/lib/ext中的類,應(yīng)用程序類加載器加載其他類,由此可分辨出一個類是否為JDK中的類,其加載器的類型選取為第二個特征。
3)保存Java類信息的文件會存放在某個路徑下,一般情況下,系統(tǒng)類存放在<JAVA_HOME>下,第三方類庫的類文件單獨存放或者和用戶自定義類存放在程序的目錄下;由于不同類型的類文件一般存放在不同的路徑下,因此本文將Java類存放的文件的路徑選取為第三個特征。
4)在程序的目錄下,大多數(shù)情況下,不管是自定義類文件還是第三方插件的類文件都會保存在Jar格式的壓縮包中,但仍然有少數(shù)類文件不在Jar包中,此類型一般屬于用戶自定義的類,選取為第四個特征。
5)一般來說,對于一個存放類文件的Jar包,如果名字可以和程序的名稱匹配或相似,這個Jar包中的類文件則是由用戶自定義開發(fā)的。類文件所在Jar包的名稱可以選取為第五個特征。
6)對于程序來說,生成項目會改變此程序的文件,從而改變了這些項目文件的最后修改時間,查看一個類文件的最后修改時間是否和程序一致,也可作為判斷類文件是否屬于程序的特征。
3.1.3 樸素貝葉斯分類模型
樸素貝葉斯分類模型是基于貝葉斯定理的分類模型,每個特征都相互獨立。樸素貝葉斯分類器的優(yōu)點在于算法簡單,需要的訓(xùn)練數(shù)據(jù)量較少,且在實際的分類中處理中資源負(fù)載很小,它的分類過程如下:
1)對于一個數(shù)據(jù)樣本X={x1,x2,…,xn},其中x1,x2,……,xn表示數(shù)據(jù)樣本 X的 n個特征。C={c1,c2,…,cn}表示n個類的集合,數(shù)據(jù)樣本X可能屬于的C中的某個類。對于未標(biāo)記的數(shù)據(jù)樣本X,其屬于類ck的概率為P(Ck|x1,x2…,xn)。
2)根據(jù)貝葉斯定理:
3)根據(jù)數(shù)據(jù)集可以訓(xùn)練得出:
4)對于未知樣本數(shù)據(jù)X,其分類為y,y∈C
3.1.4 分類器的實驗和評估
將采集的數(shù)據(jù)67 000余條用于學(xué)習(xí),10 000條用于評估。實驗結(jié)果如表1所示。
表1 貝葉斯分類器學(xué)習(xí)和預(yù)測結(jié)果
貝葉斯分類器對Java類進(jìn)行區(qū)分時,正確預(yù)測用戶類4 172個,系統(tǒng)類2 727個,第三方類,1 487個,總計個數(shù)8 406,準(zhǔn)確率可達(dá)到84%,對系統(tǒng)類的預(yù)測準(zhǔn)確率較高,準(zhǔn)確預(yù)測所有的系統(tǒng)類;可以將所有的第三方類標(biāo)記成功,但是對用戶類的預(yù)測率較低,5 766個用戶類只預(yù)測出4 172個,真實預(yù)測準(zhǔn)確率為72.35%,將一些用戶類預(yù)測為第三方類。其原因主要為一些用戶類的Jar包的名字并不與項目名稱匹配或相似,且沒有存放在目標(biāo)程序的路徑下,最后修改時間與主項目不一致。遇到此種情況時,在實際中,可以手動將一些Jar包信息加入用戶類中。
Pinpoint的是一個開源的軟件性能管理軟件[5],在它的插樁部分,對軟件不同的部分采用不同類型的插樁插件進(jìn)行對應(yīng)分析,一些常用組件都有與之對應(yīng)的插件進(jìn)行插樁分析,例如:http,mysql-jdbc,Spring等插件。本文借鑒了Pinpoint的插樁思路,采用插件式的分析器組件。目標(biāo)程序不同的部分都有與之對應(yīng)的分析插件進(jìn)行分析,所有插件分析的結(jié)果之和構(gòu)成目標(biāo)程序初始化階段插樁分析的結(jié)果。一般情況下,在初始化分析階段,本文傾向于使用盡可能粗的插樁粒度。
采用基于插件組合的插樁分析的策略,既能夠保證插樁的專業(yè)性,也能夠通過增加插件來實現(xiàn)擴(kuò)展性,從而使得本文的方法具有很強(qiáng)的靈活性。
3.3.1 插樁粒度
一個軟件可以實現(xiàn)不同的功能,例如通過網(wǎng)絡(luò)傳遞消息或者和數(shù)據(jù)庫交互,軟件實現(xiàn)不同功能的代碼執(zhí)行路徑不同,插樁粒度的情況也不相同。實現(xiàn)不同功能都應(yīng)該有不同的插樁粒度等級,本文采用插件式的插樁粒度等級,即不同插件的插樁粒度等級都有專門的劃分。
3.3.2 消息傳遞模型的插樁粒度
接下來將通過一個用戶請求瀏覽網(wǎng)頁的例子,將說明http消息傳遞的插樁粒度等級。此粒度等級不僅適用于http消息傳遞程序的插樁,也適用于分布式環(huán)境下大部分程序的其他類型消息傳遞的插樁。
如圖3所示,一個用戶客戶端請求訪問一個網(wǎng)頁,此網(wǎng)頁由兩部分的內(nèi)容組成,文字和圖片,分別來自不同的服務(wù)器。當(dāng)一個客戶端發(fā)送一個瀏覽請求Request1時,首先到達(dá)網(wǎng)頁服務(wù)器,此時網(wǎng)頁服務(wù)器會發(fā)現(xiàn)需要文字和圖片的數(shù)據(jù),于是發(fā)送請求Request2和Request3到文字服務(wù)器和圖片服務(wù)器,文字服務(wù)器和圖片服務(wù)器分別立即做出反應(yīng),將消息返還給網(wǎng)頁服務(wù)器,分別是Reply2和Reply3。然后網(wǎng)頁服務(wù)器響應(yīng)最初的請求,將請求返還到客戶端Reply1。
圖3 簡單的http消息傳遞
以上是一個簡單分布式消息傳遞過程,對于分布式程序的跟蹤實現(xiàn),Google提出的分布式系統(tǒng)跟蹤框架Dapper給予后來的開發(fā)者很大的啟發(fā)[1]。在具體的分布式消息跟蹤實現(xiàn)上[14-15],本文也采用Dapper的分布式跟蹤理念,將一個完整消息請求和返還鏈路稱為一個trace。
本文在對分布式系統(tǒng)消息傳遞插樁的最粗粒度為trace等級,即記錄完成一個完整trace所需的時間和其他信息。
第二等級的插樁粒度是server等級,即記錄一個服務(wù)器收到消息到發(fā)出消息的時間和其他信息。例如:圖3中文字服務(wù)器接收到Request2時間到發(fā)出響應(yīng)Reply2的時間。
第三等級的插樁粒度稱為method等級,即記錄一個方法執(zhí)行所需的時間和其他信息。
以上三種等級的插樁粒度在目標(biāo)程序的執(zhí)行過程中隨著程序的執(zhí)行情況改變,在達(dá)到用戶要求的情況下,確保最小的插樁資源負(fù)載。
3.3.3 特征選擇
本文的性能自適應(yīng)插樁器是利用目標(biāo)程序的性能數(shù)據(jù)作為反饋,使用基于機(jī)器學(xué)習(xí)的組合算法進(jìn)行處理,找出異常的性能數(shù)據(jù),改變其執(zhí)行路徑的插樁粒度。本文希望可以在最小的插樁資源消耗下找出異常情況,即盡可能少的提取特征去判定。
特征提取:在分布式系統(tǒng)的消息傳遞中,消息的響應(yīng)時間是衡量目標(biāo)程序的某個執(zhí)行路徑是否出現(xiàn)異常的最主要因素。所以選取數(shù)據(jù)傳輸響應(yīng)時間為主要特征。
假設(shè)一個trace的總響應(yīng)時間為T,這個trace的消息要經(jīng)過n個節(jié)點的處理,那么有:
其中:ti表示第i個節(jié)點傳輸數(shù)據(jù)的響應(yīng)時間,pi表示第i個節(jié)點上程序處理請求所需要的時間。假設(shè)P=對于不同的trace來說P是不相同的,因此無法橫向評估不同trace的時間數(shù)據(jù)。但是,對同一個trace來說,P大致上相同的,本文則通過同一個trace的歷史數(shù)據(jù)去評估此trace是否異常,此時有:
P對于同一個trace的歷史數(shù)據(jù)來說可以看作為一個常量。
影響數(shù)據(jù)傳輸響應(yīng)時間的因素有很多,例如服務(wù)器硬件性能,網(wǎng)絡(luò)帶寬等等。但是在同樣的環(huán)境下,影響數(shù)據(jù)傳輸響應(yīng)時間的因素主要為數(shù)據(jù)包的個數(shù)和大小,數(shù)據(jù)包越多,數(shù)據(jù)量越大傳輸所需要的時間越長。
在實際的傳輸過程中,傳輸時間受數(shù)據(jù)包大小和數(shù)據(jù)包個數(shù)的共同影響,數(shù)據(jù)包越少,傳輸時間越短。但是,并不能將所有數(shù)據(jù)都分在一個數(shù)據(jù)包內(nèi),因為最大數(shù)據(jù)包的大小受到最大傳輸單元的限制(MaximumTransmissionUnit),以下簡稱為MTU,MTU的單位為字節(jié),當(dāng)所傳輸?shù)臄?shù)據(jù)量大于一個MTU時,此時發(fā)送的數(shù)據(jù)就會被分為多個包,每個包的大小都不大于MTU。
如圖4所示,圖中為trace總響應(yīng)時間和數(shù)據(jù)包大小的關(guān)系,其中X軸代表數(shù)據(jù)包含有MTU的個數(shù),Y軸代表trace響應(yīng)的時間。MTU_s是發(fā)送一個足夠小的數(shù)據(jù)包需要的時間,MTU_e代表發(fā)送一個大小為MTU的數(shù)據(jù)包所需要的時間。當(dāng)x的區(qū)間為 (k,k+1)時,響應(yīng)時間Y與x成正比例遞增關(guān)系,每當(dāng)數(shù)據(jù)量超出一個MTU時,響應(yīng)時間就會因為多出一個數(shù)據(jù)包而增加MTU_s,趨勢呈現(xiàn)階梯形增長。
圖4 trace總響應(yīng)時間和包大小的關(guān)系
當(dāng)一個trace中傳輸?shù)臄?shù)據(jù)量大小超過MTU時就會被分成k個盡可能大的子包,每個子包的大小不超過MTU,其總響應(yīng)時間在區(qū)間之間。
影響trace的響應(yīng)時間的因素分別是數(shù)據(jù)量的大小,包的個數(shù)與MTU的大小。選擇數(shù)據(jù)量的大小為第二個特征。MTU的大小在一般的生成環(huán)境中是固定不變的,對其視為常量,不選為特征。
在實際的生產(chǎn)環(huán)境中,傳輸數(shù)據(jù)時會設(shè)有數(shù)據(jù)緩沖區(qū),當(dāng)緩存的數(shù)據(jù)足夠接近一個MTU大小時才會發(fā)送。假設(shè)傳輸數(shù)據(jù)量為Size,數(shù)據(jù)包的個數(shù)為Num,則Num有以下式子表示:
雖然此時得到的Num會與實際Num有一定誤差,但誤差尚在可容忍范圍內(nèi)。若選取數(shù)據(jù)包數(shù)量為特征,則需要大量的插樁點獲取數(shù)據(jù),會造成很大的資源負(fù)載。綜合考慮到實際插樁資源的消耗,盡可能的減少插樁點,所以不選數(shù)據(jù)包數(shù)量為特征。
3.3.4 性能數(shù)據(jù)線性回歸模型
由上一節(jié)可知,響應(yīng)時間和數(shù)據(jù)傳輸量是分布式系統(tǒng)消息傳遞的特征,采集到的trace的性能數(shù)據(jù)將是一個響應(yīng)時間和數(shù)據(jù)傳輸量的數(shù)據(jù)集合,記為:
其中:(xi,yi)表示當(dāng)數(shù)據(jù)量大小為xi個MTU時,其響應(yīng)時間為yi。當(dāng)x發(fā)生改變時,y隨著x的改變而改變,當(dāng)x在區(qū)間為 (k,k+1)內(nèi)時,其中的正常數(shù)據(jù)點可以通過一元線性回歸模型擬合成一條直線段,稱為性能擬合回歸線,如圖5所示。
圖5 性能擬合回歸線
線性回歸模型的模型函數(shù)為:
對以上參數(shù)利用最小二乘法進(jìn)行估計:
其中:
有r表示樣本x與y之間的相關(guān)系數(shù),用來衡量擬合的回歸的好壞。
回歸方程的誤差為ε,ε~ (0,σ2),利用離差平方和計算誤差:
3.3.5 噪聲處理
如圖5所示,trace的性能數(shù)據(jù)不僅包括正常的數(shù)據(jù)點,而且包括出現(xiàn)性能異常的數(shù)據(jù)點。出現(xiàn)性能異常的情況一般包括兩種:延遲和超時。延遲指出現(xiàn)性能問題,雖然能夠成功傳輸數(shù)據(jù),但是因為硬件環(huán)境、網(wǎng)絡(luò)等緣故出現(xiàn)明顯的響應(yīng)時間過大情況。超時指不能夠成功傳輸數(shù)據(jù)的情況,一般的超時時間遠(yuǎn)遠(yuǎn)大于發(fā)送單個數(shù)據(jù)包的時間。在圖5中,除了正常的數(shù)據(jù)點外 (在圖中用圓形表示),還包括2個延時數(shù)據(jù)點 (在圖中用矩形表示),2個超時數(shù)據(jù)點,(在圖中用叉點表示)。超時時間Out_time>>MTU_e>MTU_s。
當(dāng)用性能數(shù)據(jù)擬合成性能回歸線時,由于異常數(shù)據(jù)點的存在,會對擬合成的線造成極大的影響,無法反應(yīng)正常的情況,所以就需要將這些異常的點或者疑似異常的點去掉,由正常數(shù)據(jù)點的集合擬合成性能回歸線。
K-Means算法是一種經(jīng)典的基于距離的聚類算法,采用距離作為相似性評價指標(biāo),可以將N個數(shù)據(jù)對象劃分為K個類,同一類對象之中,對象之間的相似度較高,不同類對象之間的相似度較小。本文在K-Means算法的基礎(chǔ)上對其進(jìn)行改進(jìn),改變相似度的計算方式,工作流程如下:
1)從N個數(shù)據(jù)對象中隨機(jī)選擇K個質(zhì)心。
3)重新計算每個類的質(zhì)心
4)重復(fù)第2步和第3步,直到質(zhì)心不在變化或者變化小于預(yù)先設(shè)定的閾值。
除此而外,使用改進(jìn)的K-Means算法,簡單、快速,造成的資源負(fù)載較小。
3.3.6 數(shù)據(jù)預(yù)處理
為了方便對性能數(shù)據(jù)進(jìn)行判別,就需要對原始數(shù)據(jù)進(jìn)行預(yù)處理。原始的性能數(shù)據(jù)將是一個響應(yīng)時間和數(shù)據(jù)傳輸量的數(shù)據(jù)集合O,O={(size1,time1),(size2,time2),…,(sizei,timei)}。
size表示傳輸數(shù)據(jù)量的大小,time表示響應(yīng)時間。根據(jù)上文可知,僅當(dāng)數(shù)據(jù)量大小范圍在 (k,k+1)MTU時,數(shù)據(jù)點可擬合為一條線段,則記x為含有MTU的個數(shù)。根據(jù)上文可知:
x在每個范圍 (k,k+1)都能擬合成線段,而且擬合的線段斜率相等,因為其擬合線段的相似性,可以歸納得一般的模型,利于處理,此時將要求x的范圍 (0,1),相應(yīng)地對time項進(jìn)行預(yù)處理得到:
最后,預(yù)處理后的性能數(shù)據(jù)集合為:
3.3.7 性能數(shù)據(jù)處理過程
算法輸入:原始性能數(shù)據(jù)集
步驟1:生成預(yù)處理性能數(shù)據(jù)集
步驟2:使用K-Means算法對數(shù)據(jù)進(jìn)行分類,SetK=3,將原始數(shù)據(jù)分為三類,一類為正常數(shù)據(jù),一類為延遲據(jù),一類為超時數(shù)據(jù)
Get P1={(x1,y1),(x2,y2),…,(xi,yi)},x∈(0,1)為正常數(shù)據(jù)類
Get P2={(x1,y1),(x2,y2),…,(xi,yi)},x∈(0,1)為延遲數(shù)據(jù)類
Get P3={(x1,y1),(x2,y2),…,(xi,yi)},x∈(0,1)為延遲數(shù)據(jù)類
步驟3:處理P1數(shù)據(jù)集,將P1數(shù)據(jù)集中y大于MTU_e的數(shù)據(jù)點去除,得到數(shù)據(jù)集合P4。
步驟4:使用P4進(jìn)行一元線性回歸擬合,得到性能擬合回歸線:
步驟5:使用性能擬合回歸線對P1、P2進(jìn)行判斷
設(shè)置異常數(shù)據(jù)集R
foreach(xi)do
{if);
elseR.a(chǎn)dd();}
Get為異常數(shù)據(jù)集合
算法輸出:異常數(shù)據(jù)集合
3.3.8 自適應(yīng)器異常判斷的實驗和評估
本實驗數(shù)據(jù)來源主要是對遠(yuǎn)程網(wǎng)路地址發(fā)送報文,并記錄報文大小和響應(yīng)時間。
經(jīng)過多次實驗,本文實驗環(huán)境中,MTU大小為1 500字節(jié),MTU_s約為2 ms,MTU_e約為4 ms。向遠(yuǎn)程服務(wù)器發(fā)送不同大小的數(shù)據(jù)包,共發(fā)送6 000條報文,得到報文大小和響應(yīng)時間,用基于組合機(jī)器學(xué)習(xí)算法的自適應(yīng)器進(jìn)行處理,得到表2。
基于組合機(jī)器學(xué)習(xí)算法自適應(yīng)器對性能數(shù)據(jù)進(jìn)行預(yù)測,正確預(yù)測正常點4 656個,延遲點327個,超時點344個,總計個數(shù)為5 327,正確率為88.78%。其對超時數(shù)據(jù)點和延遲數(shù)據(jù)點的都能夠完全準(zhǔn)確判別。但是對數(shù)據(jù)的延遲十分敏感,將一些正常的數(shù)據(jù)點判別為延遲數(shù)據(jù)點,這是由于在數(shù)據(jù)處理的過程中,為了處理噪音,盡可能的保留擬合回歸線的數(shù)據(jù)點,得到的誤差偏小,將一些波動較大的正常數(shù)據(jù)點判別為延遲數(shù)據(jù)點,這樣會導(dǎo)致插樁點的數(shù)量變多。在實際的插樁過程中,可以根據(jù)實際的延遲容忍情況,可以在數(shù)據(jù)點判別時,適當(dāng)增大誤差值,減少插樁點。
表2 性能數(shù)據(jù)處理結(jié)果
在本章,本文將使用自適應(yīng)插樁架構(gòu)對的目標(biāo)程序進(jìn)行插樁,并與Pinpoint的插樁策略在同等的條件下的插樁情況進(jìn)行對比,評估本文插樁架構(gòu)對目標(biāo)程序產(chǎn)生的影響和對系統(tǒng)產(chǎn)生的影響。
為了驗證本文插樁架構(gòu),實驗的目標(biāo)程序是基于Java開源電子商務(wù)網(wǎng)站架構(gòu)Broadleaf的購物網(wǎng)站HeatClinic[16]。HeatClinic是一個多層架構(gòu)的網(wǎng)站,主要使用SpringBoot框架開發(fā)。實驗所使用的數(shù)據(jù)庫為MySQL,網(wǎng)站的部署在Tomcat應(yīng)用服務(wù)器上。
本文對HeatClinic網(wǎng)站的業(yè)務(wù)進(jìn)行實驗,將通過壓力測試工具JMeter對插樁過后的HeatClinic網(wǎng)站進(jìn)行測試,查看不同壓力規(guī)模下,對目標(biāo)程序和系統(tǒng)產(chǎn)生的影響。主要查看網(wǎng)頁響應(yīng)時間和吞吐量。網(wǎng)頁的響應(yīng)時間來衡量監(jiān)控工具對目標(biāo)程序的干擾情況,吞吐量來衡量插樁資源負(fù)載情況。壓力測試中,線程在100 ms內(nèi)完成啟動。
本文實現(xiàn)兩種不同情況的實驗:1)正常情況下的實驗,即網(wǎng)站沒有發(fā)生異常。2)異常情況下的實驗,即網(wǎng)站出現(xiàn)異常產(chǎn)生響應(yīng)延時,此種情況可以由向網(wǎng)站注入線程延遲代碼模擬。本文將在不同的方法中注入延遲時長不等的延遲點。
4.3.1 插樁對應(yīng)用性能造成干擾的分析
圖6顯示了在相同并發(fā)用戶數(shù)的情況下,不同插樁策略的網(wǎng)頁平均響應(yīng)時間隨著發(fā)生異常的變化情況。在目標(biāo)程序運(yùn)行初時,Pinpoint和本文分別對目標(biāo)程序進(jìn)行插樁,使目標(biāo)程序正常運(yùn)行一段時間,得到目標(biāo)程序正常運(yùn)行的性能數(shù)據(jù)范圍。本文插樁架構(gòu)完成初始化階段并進(jìn)入自適應(yīng)階段。在程序運(yùn)行到一段時間后,圖中為a時間點,動態(tài)的對相關(guān)方法注入延遲代碼,造成異常,產(chǎn)生響應(yīng)延遲Δt。使用全插樁策略的Pinpoint和本文的插樁不會改變插樁粒度,目標(biāo)程序網(wǎng)頁的平均響應(yīng)時間增加Δt。本文的自適應(yīng)插樁在目標(biāo)程序發(fā)生異常后收集到性能數(shù)據(jù),并分析得出程序發(fā)生異常,在a時后的一段時間,使程序的插樁粒度更細(xì),增加插樁點。此時目標(biāo)程序的網(wǎng)頁平均響應(yīng)時間增量超過Δt,達(dá)到全插樁策略。在程序運(yùn)行到b時,去除延遲代碼,本文自適應(yīng)插樁收集到性能數(shù)據(jù)并分析得出程序運(yùn)行正常,調(diào)整插樁粒度,減少插樁點,對目標(biāo)程序的干擾變小,平均響應(yīng)時間減少。本文的自適應(yīng)策略與Pinpoint全插樁策略相比,在對目標(biāo)程序監(jiān)控時,平均的響應(yīng)時間更少。
圖6 發(fā)生異常時的網(wǎng)頁響應(yīng)時間的變化情況
4.3.2 插樁資源負(fù)載分析
圖7顯示了在相同并發(fā)用戶數(shù)的情況下,且計算資源充足,不同插樁策略的網(wǎng)頁平均吞吐量隨著發(fā)生異常的變化情況。在a時刻注入異常代碼,隨著響應(yīng)時間增大,無插樁以及Pinpoint全插樁和本文全插樁的網(wǎng)頁吞吐量會受到影響并減少,對于本文自適應(yīng)策略來說,發(fā)生異常初始,吞吐量減少,但隨著診斷出異常并增加插樁點后,需求系統(tǒng)資源變多,吞吐量增大。在b時刻移除異常后,各個策略的吞吐量均會恢復(fù)到正常水平。
圖7 發(fā)生異常時網(wǎng)頁吞吐量的變化情況
4.3.3 不同插樁策略對比分析
表3展示不同策略下目標(biāo)程序網(wǎng)頁的平均響應(yīng)時間和吞吐量。使用 Pinpoint和對目標(biāo)程序進(jìn)行插樁,10、50、100用戶并發(fā)平均響應(yīng)時間比不使用插樁分別多了22.9%、15.38%、8.98%,使用本文的自適應(yīng)插樁架構(gòu)的情況為13.63%,7.17%,2.81%。使用本文的自適應(yīng)插樁比使用Pinpoint全插樁平均的響應(yīng)時間要減小6.88%,本文的插樁策略對目標(biāo)程序造成的干擾更小。使用本文自適應(yīng)插樁,在目標(biāo)程序的運(yùn)行過程中,平均網(wǎng)頁吞吐量更小,平均插樁點比Pinpoint全插樁少,需要的系統(tǒng)資源更少。
表3 不同插樁策略對比
在分布式系統(tǒng)中,對于分布式應(yīng)用出現(xiàn)的異常進(jìn)行診斷和定位是十分重要的。在對這些異常進(jìn)行診斷的過程中,現(xiàn)今研究主要目標(biāo)是將插樁的資源消耗和對目標(biāo)程序的干擾降低到最小,可以使插樁技術(shù)在實際的大規(guī)模分布式系統(tǒng)的中得到應(yīng)用。雖然一些插樁工具被一些大公司使用在其生產(chǎn)環(huán)境,如 Twitter、Google、京東、阿里巴巴等,但是,到目前為止大部分的研究工作將重點放在了改進(jìn)插樁過程的本身[17],或者以不同的原理實現(xiàn)分布式跟蹤[18-19],如Facebook最新的工作[20],達(dá)到優(yōu)化插樁的目的。但是,插樁資源的主要消耗和對系統(tǒng)的干擾來自于產(chǎn)生和收集大量的性能數(shù)據(jù),在插樁運(yùn)行的過程中,自適應(yīng)調(diào)節(jié)插樁點可以有效的減小插樁資源消耗與干擾。
現(xiàn)今仍然有一部分研究工作針對自適應(yīng)插樁,AIM[6]是針對于軟件性能分析的自適應(yīng)插樁框架,它能夠在插樁過程中依靠指令增加和減少插樁點,但是指令卻需要通過其客戶端人工輸入。DOBI[17]兼顧了插樁的精準(zhǔn)性、完整性和性能消耗,使用基于方法執(zhí)行時間的分析的模型進(jìn)行自適應(yīng)插樁,但是插樁粒度過細(xì),產(chǎn)生大量消耗。RaceTrack[21]是針對.net框架下程序異常診斷的自適應(yīng)插樁框架,主要對線程進(jìn)行分析,對異常線程加入更多的插樁點,問題在于在大規(guī)模的分布式系統(tǒng)中線程眾多且復(fù)雜,并且難以收集綜合分析。APMP[22]對每個插樁點都產(chǎn)生權(quán)重,通過權(quán)重分析決策增減插樁點,缺點在于仍然需要大量的信息支持,會造成不少資源消耗。SSSM[24]提出了一種利用性能數(shù)據(jù)進(jìn)行響應(yīng)時間預(yù)測的策略,為了判定異常,采用了多個維度、細(xì)粒度的響應(yīng)時間回收策略,進(jìn)而產(chǎn)生大量的插樁點。本文認(rèn)為在復(fù)雜多變的分布式環(huán)境下,很難通過一種或多種預(yù)先設(shè)定的策略或標(biāo)準(zhǔn)去判斷發(fā)生性能延遲,使用機(jī)器學(xué)習(xí)算法,學(xué)習(xí)實際的數(shù)據(jù),分析性能曲線,是一種客觀有效的方法。
本文提出了一個基于機(jī)器學(xué)習(xí)的自適應(yīng)插樁框架,在對目標(biāo)程序運(yùn)行的過程中,不僅能夠動態(tài)地進(jìn)行插樁,而且能夠依據(jù)目標(biāo)程序的性能數(shù)據(jù)動態(tài)的調(diào)整插樁粒度,在診斷異常的同時,將插樁資源消耗減少到最小。未來的研究方向主要是以下幾個方面:1)將對現(xiàn)有性能數(shù)據(jù)處理的算法進(jìn)行改進(jìn),主要提高對異常數(shù)據(jù)點識別的準(zhǔn)確率和降低處理算法的資源消耗。2)完善自適應(yīng)規(guī)則,對于不同類型的軟件所產(chǎn)生的性能數(shù)據(jù)是不一樣,所以就應(yīng)該有相對應(yīng)的、更專業(yè)的自適應(yīng)規(guī)則去匹配,希望通過插件的形式可以完善自適應(yīng)規(guī)則,保持本文的活力。3)采樣策略,雖然本文在極力減少插樁點,但是這些插樁點也會產(chǎn)生很多性能數(shù)據(jù),有些性能數(shù)據(jù)價值較大,有些價值較小[21],如何在保持性能消耗不變甚至減小消耗的情況將有價值大數(shù)據(jù)點辨別出來并記錄下來,拋棄無價值的數(shù)據(jù)點也是我們未來的工作之一[25]。
本文提出了一種全新的自適應(yīng)插樁架構(gòu),其核心是利用機(jī)器學(xué)習(xí)算法分析性能數(shù)據(jù)并根據(jù)得到反饋對Java程序進(jìn)行動態(tài)插樁,本文的方法依據(jù)目標(biāo)程序?qū)嶋H運(yùn)行情況動態(tài)調(diào)整插樁粒度。本文解決了用戶在生產(chǎn)環(huán)境中應(yīng)用執(zhí)行狀態(tài)追蹤和性能監(jiān)測中的兩個主要的問題:第一,用戶往往只關(guān)心他們自己編寫的類的運(yùn)行情況,而對于其他來源的類,插樁會造成較大的資源負(fù)載,所以本文提出基于樸素貝葉斯算法的分類器,只將用戶自己編寫的類找出并分析它們,減小了插樁資源消耗。第二個問題是傳統(tǒng)的插樁工具往往不能令人滿意,插樁粒度粗則會導(dǎo)致無法定位異常,插樁粒度細(xì)則會導(dǎo)致資源負(fù)載過大。本文將基于機(jī)器學(xué)習(xí)的組合算法引入性能數(shù)據(jù)處理,分析性能數(shù)據(jù)并動態(tài)調(diào)整插樁粒度。