自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一波三折,APM監(jiān)控系統(tǒng)對(duì)于OSGI架構(gòu)的探索實(shí)踐

原創(chuàng)
開發(fā) 架構(gòu)
當(dāng)我們的監(jiān)控系統(tǒng)遇到了OSGI架構(gòu)系統(tǒng)也是碰撞出了激烈的火花,足足用了兩天的時(shí)間,才搞定了各種水土不服。我把期間的各種酸甜苦辣記錄下來,希望為奮斗在APM一線的同行們提供一點(diǎn)點(diǎn)幫助。

2017年我們一直在做分布式服務(wù)跟蹤系統(tǒng)的升級(jí)改造,為了更好的服務(wù)于廣大的開發(fā)和運(yùn)維人員,能夠在數(shù)以千萬計(jì)的微服務(wù)系統(tǒng)中快速的發(fā)現(xiàn)問題、定位問題。一年下來,接入了上千個(gè)系統(tǒng),快的幾分鐘,慢的不到10分鐘,最多就是處理一下jar包依賴沖突問題,所以能這么迅速的推廣。不過前幾天遇到一個(gè)客戶,他們的系統(tǒng)采用的是OSGI的框架。OSGI這個(gè)詞相信大部分人是聽過沒用過的。

當(dāng)我們的監(jiān)控系統(tǒng)遇到了OSGI架構(gòu)系統(tǒng)也是碰撞出了激烈的火花,足足用了兩天的時(shí)間,才搞定了各種水土不服。我把期間的各種酸甜苦辣記錄下來,希望為奮斗在APM一線的同行們提供一點(diǎn)點(diǎn)幫助。

在整個(gè)過程中,我們遇到了四個(gè)技術(shù)關(guān)鍵點(diǎn):

  • 把一個(gè)普通的jar包Bundle化。
  • OSGI的類加載機(jī)制。
  • 引入第三方非Bundle化的jar包。
  • 通過Activator在Bundle的啟動(dòng)和停止實(shí)現(xiàn)回調(diào)。

項(xiàng)目背景

首先我簡(jiǎn)單介紹下兩個(gè)項(xiàng)目的背景:

監(jiān)控系統(tǒng):這是一個(gè)利用動(dòng)態(tài)字節(jié)碼技術(shù),自動(dòng)的無侵入的對(duì)目標(biāo)系統(tǒng)實(shí)行秒級(jí)實(shí)時(shí)監(jiān)控,監(jiān)控內(nèi)容包括但不限于容量、性能、成功率、調(diào)用鏈、應(yīng)用拓?fù)涞?,詳?xì)內(nèi)容請(qǐng)參考:http://os.51cto.com/art/201709/552641.htm。

目標(biāo)系統(tǒng):分為兩部分,第一部分以war包形式發(fā)布,部署到Web容器內(nèi),接收http請(qǐng)求;第二部分采用OSGI架構(gòu),每一個(gè)Bundle是一個(gè)業(yè)務(wù)服務(wù)(可以理解為Bundle就是微服務(wù))。這兩部分通過servlet的橋接方式連接,其中一些公用Bundle(如slf4j)會(huì)放在war包的指定目錄中,每個(gè)業(yè)務(wù)Bundle不需要再單獨(dú)引入。這個(gè)架構(gòu)是我在解決了所有問題之后才總結(jié)出來的,在這里提前拋出是為了方便理解后邊的內(nèi)容。

剛開始的時(shí)候,我們就把它當(dāng)做一個(gè)普通的系統(tǒng)來接入,把我們的jar包放到了目標(biāo)系統(tǒng)war包的WEB-INF/lib下,加入啟動(dòng)腳本,再啟動(dòng)系統(tǒng),可以看到我們的jar打印出的啟動(dòng)日志,然后就沒有然后了,除了那一行日志,沒有任何效果。這個(gè)時(shí)候,我就意識(shí)到了OSGI系統(tǒng)不是這么玩的,于是開始OSGI的漫漫探索之路......

當(dāng)看到OSGI的每一個(gè)Bundle包都是用單獨(dú)ClassLoader負(fù)責(zé)加載時(shí),我仿佛找到了解決方案。

我們把監(jiān)控jar包打到了每一個(gè)業(yè)務(wù)的Bundle里邊,然后重啟,結(jié)果監(jiān)控成功了,問題解決了。

從純技術(shù)角度來看貌似是沒什么問題,然而噩夢(mèng)才剛剛開始。由于監(jiān)控jar包打入了業(yè)務(wù)Bundle內(nèi),帶來了兩個(gè)比較棘手的問題:第一,技術(shù)上每個(gè)業(yè)務(wù)Bundle都需要這么去引入,加大了開發(fā)工作量,這個(gè)和我們極致的設(shè)計(jì)理念是不符的;第二,業(yè)務(wù)上每一個(gè)業(yè)務(wù)Bundle就成為了一個(gè)單獨(dú)應(yīng)用,客戶方表示我們這個(gè)是一個(gè)應(yīng)用,而不是多個(gè)應(yīng)用。

所以還是要解決之前的問題,把整個(gè)目標(biāo)系統(tǒng)所有的Bundle當(dāng)做一個(gè)應(yīng)用去接入監(jiān)控。

我不熟悉目標(biāo)系統(tǒng)和OSGI架構(gòu),客戶開發(fā)方又不了解監(jiān)控系統(tǒng)的原理和實(shí)現(xiàn)過程,經(jīng)過了長時(shí)間的各種嘗試和交流,仍沒有絲毫進(jìn)展。于是我們決定采用一個(gè)最原始,最簡(jiǎn)單,最笨,也是最有效的方法,從“hello world”開始。

當(dāng)一個(gè)系統(tǒng)出現(xiàn)問題,你又不知道問題出在哪部分的時(shí)候,要么把這些“部分”一個(gè)一個(gè)去掉,直到發(fā)現(xiàn)沒有“問題”為止;要么從“零”開始,然后一個(gè)一個(gè)的加入,直到出現(xiàn)“問題”為止,我們采取了后者。

首先,針對(duì)我們的監(jiān)控jar包去掉基于動(dòng)態(tài)字節(jié)碼的自動(dòng)監(jiān)控,采用編程式的直接API調(diào)用,并且將API接口做了Mock,去掉了所有的第三方依賴。修改目標(biāo)系統(tǒng)的代碼,在希望被監(jiān)控的方法中直接調(diào)用監(jiān)控API。打包,部署,啟動(dòng),被監(jiān)控Bundle不能啟動(dòng),報(bào)錯(cuò)......

  1. Missing Constraint: Import-Package: com..................sgm...... 

經(jīng)過和對(duì)方開發(fā)人員的溝通和網(wǎng)上查閱OSGI相關(guān)資料,了解到這是一個(gè)OSGI打包的規(guī)范問題。

之前我們的監(jiān)控jar包是沒有按照OSGI的規(guī)范去打包的,打的只是一個(gè)普通的jar包,所以在OSGI框架下,其他的Bundle是無法訪問到我們的jar包中的類,造成啟動(dòng)報(bào)錯(cuò)的問題。這里遇到了我們第一個(gè)要解決的主要問題:

1. 普通jar包Bundle化

先看下邊的兩張圖:

 

上邊的兩張圖是jar包中META-INF/MANIFEST.MF文件,第一張圖是普通jar包,第二圖是具有OSGI規(guī)范的jar包。既然知道了,那這樣的OSGI的包怎么打?我們用maven來編譯,順理成章的就找到了maven-Bundle-plugin的這個(gè)插件,使用很簡(jiǎn)單,代碼如下:

  1. <plugin> 
  2. <groupId>org.apache.felix</groupId> 
  3. <artifactId>maven-Bundle-plugin</artifactId> 
  4. <version>3.3.0</version> 
  5. <extensions>true</extensions> 
  6. <executions> 
  7. <execution> 
  8. <id>Bundle-manifest</id> 
  9. <phase>process-classes</phase> 
  10. <goals> 
  11. <goal>manifest</goal> 
  12. </goals> 
  13. </execution> 
  14. </executions> 
  15. <configuration> 
  16. <instructions> 
  17. <Import-Package>org.slf4j</Import-Package> 
  18. <Export-Package>com.wangyin.sgm.client</Export-Package> 
  19. <Bundle-RequiredExecutionEnvironment>JavaSE-1.6</Bundle-RequiredExecutionEnvironment> 
  20. </instructions> 
  21. </configuration> 
  22. </plugin>

在這么多屬性中, Import-Package和Export-Package尤其重要,一個(gè)jar包就是一個(gè)Bundle,Bundle和Bundle之間的訪問全靠這兩個(gè)屬性來控制。

Import-Package:說明了這個(gè)Bundle jar需要調(diào)用外部其他Bundle的哪些包(package)。

Export-Package: 說明了這個(gè)Bundle jar可以提供哪些包(package)供其他Bundle去調(diào)用。

當(dāng)一個(gè)Bundle啟動(dòng)的時(shí)候,會(huì)分為Resolved(解析),Installed(安裝),Started(激活)等幾個(gè)步驟,解析就是檢查jar是不是正常,安裝的時(shí)候會(huì)把Export-Package中的指定的包進(jìn)行注冊(cè),這就知道哪些包由哪些Bundle來提供,激活的時(shí)候會(huì)檢查當(dāng)前這個(gè)Bundle的Import-Package依賴的包是不是都有對(duì)應(yīng)的Bundle來提供,否則激活失敗。

修改監(jiān)控客戶端,把父項(xiàng)目,子項(xiàng)目,子子項(xiàng)目全部按照OSGI規(guī)范打包,又是一遍部署重啟,這次被監(jiān)控的業(yè)務(wù)Bundle沒有報(bào)錯(cuò),而是監(jiān)控客戶端啟動(dòng)報(bào)錯(cuò):

  1. Missing Constraint: Import-Package: org.slf4j 

有了剛才的經(jīng)驗(yàn),一看就知道是沒有slf4j,可是對(duì)方開發(fā)人員反饋slf4j是有的,他們自己也在用,這就奇怪了。

這時(shí)我發(fā)現(xiàn)還有在監(jiān)控客戶端里MANIFEST.MF的一句話org.slf4j;version="[1.7,2)",version這個(gè)詞引起了我的注意。

原來在目標(biāo)系統(tǒng)里的slf4j是1.5版本,而監(jiān)控客戶端要求1.7版本,slf4j降級(jí)再試,這次目標(biāo)系統(tǒng)成功調(diào)用到了監(jiān)控客戶端的API,打印出了日志,萬里長征終于邁出了第一步。但這只是一個(gè)Mock的版本,真正的監(jiān)控程序還沒有加入。

先小結(jié)一下:首先在OSGI的框架下,所有jar必須按照OSGI的規(guī)范去打包,否則不能使用;其次要對(duì)Import-Package和Export-Package配置好,需要依賴外部哪些包,自己又可以提供哪些包供外部使用,同時(shí)注意版本,依賴時(shí)一般都是要高于哪個(gè)版本。

接下來,就要加入真正的代碼跑一跑,根據(jù)前面的經(jīng)驗(yàn)還是要慢慢來,我們分兩步加入代碼,第一次先加入監(jiān)控代碼,待成功后加入網(wǎng)絡(luò)傳輸部分的代碼,后邊遇到的坑讓我們發(fā)現(xiàn)這個(gè)決定是正確的。

打包部署啟動(dòng)調(diào)用這個(gè)流程已經(jīng)很熟練了,報(bào)錯(cuò)還是如期出現(xiàn)了,這次報(bào)錯(cuò)的是com.lmax的disruptor這個(gè)第三方開源jar包:

  1. Missing Constraint: Import-Package: sun.misc 

納尼?sun.misc沒有?這不是jdk里的嗎,為什么會(huì)沒有呢?于是,又是一番資料查找,終于明白了其中的道理。

這里就要講到OSGI的類加載機(jī)制了,它并不是我們常說的雙親委托機(jī)制。關(guān)于這個(gè)問題,網(wǎng)上已經(jīng)有很多文章介紹了,但這里我還是結(jié)合這個(gè)例子簡(jiǎn)單的說一下。

2. OSGI的類加載機(jī)制

首先每一個(gè)Bundle(jar)都會(huì)被一個(gè)單獨(dú)的ClassLoader去加載,當(dāng)一個(gè)Bundle的ClassLoader嘗試去加載一個(gè)類的時(shí)候:

  1. 如果這個(gè)類的包名是java.*開頭的,那么直接交給bootstrap ClassLoader去加載。
  2. 查看這個(gè)類是否在OSGI的配置文件中有org.osgi.framework.bootdelegation屬性定義,如果定義了,交給bootstrap ClassLoader去加載這個(gè)類。
  3. 查看這個(gè)類是否在OSGI的配置文件中有org.osgi.framework.system.packages屬性定義,如果定義了,交給父類加載器去加載,一般就是AppClassLoader。
  4. 查看這個(gè)類是否在本Bundle的MANIFEST.MF文件的Import-Package中定義,如果定義了,交給Export-Package這個(gè)類的Bundle去加載。
  5. 在上邊條件都不滿足的時(shí)候,那這個(gè)類就是自己的Bundle的內(nèi)部類,由自己的ClassLoader去加載。

現(xiàn)在我們來看一下,下邊這張圖是disruptor的MANIFEST.MF文件:

在disruptor里使用了sun.misc.Unsafe類,在啟動(dòng)disruptor的時(shí)候,需要加載sun.misc.Safe,那么1,2,3點(diǎn)都不滿足,命中了第4點(diǎn),就開始尋找?guī)в蠩xport-Package的Bundle,那肯定找不到。

遵循上邊的原則,在配置文件中加入了org.osgi.framework.bootdelegation=javax.*,sun.*,同時(shí)又把disruptor中的Import-Package刪掉了,問題成功解決了,又向勝利邁進(jìn)了一步。

接下來,就要把網(wǎng)絡(luò)傳輸這部分加入進(jìn)來了,系統(tǒng)就可以監(jiān)控了,數(shù)據(jù)需要發(fā)送出來才可以真正的使用。有了之前的經(jīng)驗(yàn),感覺應(yīng)該問題不大,然而現(xiàn)實(shí)情況并不是這樣……

當(dāng)把這部分代碼和依賴加入后,各種的Missing Constraint: Import-Package: xxx。我發(fā)現(xiàn)所有的依賴的第三方j(luò)ar都在里邊,為什么還會(huì)報(bào)錯(cuò),我開始懷疑是這些第三方j(luò)ar本身的問題。

打開這些jar文件的MANIFEST.MF文件查看,果然如此,我們依賴的這些jar有多一半都不是Bundle化的jar包,這里就涉及到了本文第三個(gè)要解決的核心問題。

3. OSGI如何加載第三方非Bundle化的jar包

OSGI如何加載第三方非Bundle化的jar包,有如下幾種方式:

  • 通過父類加載器加載,也就是配置org.osgi.framework.system.packages。
  • 將jar轉(zhuǎn)換成Bundle,然后Export-Package。
  • 把jar打包進(jìn)引用方的Bundle。

第一種方式需要目標(biāo)系統(tǒng)配置,同樣不符合我們的設(shè)計(jì)理念,顯然不合適,于是我們首先嘗試了第二種方式,重新打包那些非Bundle的第三方j(luò)ar。

在這個(gè)過程中,我們發(fā)現(xiàn)這絕對(duì)是個(gè)苦逼的活,需要找到源碼,下載源碼,修改pom,有的找源碼很費(fèi)勁,有的還是ant編譯的,有的雖然是maven管理,但又依賴了父項(xiàng)目……

總之想順利的重新打包是個(gè)很費(fèi)勁的事??磥碇皇O碌谌龡l路了,這又要退回到之前第一個(gè)問題,如何打一個(gè)Bundle jar。

之前我們是把maven工程的每一個(gè)子項(xiàng)目分別Bundle化,如果要把第三方j(luò)ar打入Bundle,那就有可能一個(gè)第三方j(luò)ar被多次打入不同的子項(xiàng)目Bundle,造成浪費(fèi)。

所以決定放棄對(duì)原有項(xiàng)目的每個(gè)子項(xiàng)目單獨(dú)Bundle化的方案,而是新建一個(gè)子項(xiàng)目,由這個(gè)子項(xiàng)目引入所有的其他子項(xiàng)目和第三方依賴jar,把他們所有打成一個(gè)大的Bundle jar。

  1. <Import-Package>org.osgi.framework,org.slf4j</Import-Package> 
  2. <Export-Package>com.wangyin.sgm.client</Export-Package> 
  3. <Private-Package>com.wangyin.*,com.lmax.disruptor.*,…… 
  4. </Private-Package>

看一下上邊的配置,比之前多了一個(gè)Private-Package,就是說哪些Package是這個(gè)Bundle的內(nèi)部包,也就是要打入最后Bundle jar的東西。

現(xiàn)在通過編程式API直接調(diào)用的方式已經(jīng)可以監(jiān)控到目標(biāo)系統(tǒng)了,最后要做的就是引入運(yùn)行時(shí)字節(jié)碼增強(qiáng)技術(shù)。

還是按照常規(guī)的方式,把我們的Agent通過javaagent方式啟動(dòng),有了之前的經(jīng)驗(yàn),我知道這個(gè)是被bootstrap ClassLoader加載的,于是就直接在org.osgi.framework.bootdelegation中加入了監(jiān)控Agent的包名。

結(jié)果啟動(dòng)正常,但是無法自動(dòng)監(jiān)控,看了日志后發(fā)現(xiàn)是監(jiān)控客戶端沒有啟動(dòng)。監(jiān)控客戶端的啟動(dòng)是通過在每個(gè)類加載的時(shí)候,嘗試性的使用它的類加載去加載監(jiān)控客戶端的啟動(dòng)類。

如果可以加載上,那么就啟動(dòng)成功了,因?yàn)閱?dòng)程序是放在了啟動(dòng)類的static塊中,且啟動(dòng)類是一個(gè)單例模式,記錄著被監(jiān)控應(yīng)用的信息。

從這個(gè)啟動(dòng)日志來分析,被監(jiān)控系統(tǒng)有200多個(gè)Bundle,每個(gè)Bundle啟動(dòng)都去加載監(jiān)控客戶端,然后我們希望監(jiān)控客戶端被它自己的類加載器去加載。這里就是本文第四個(gè)核心問題。

4. 如何在Bundle啟動(dòng)的時(shí)候去做一些初始化操作

在OSGI的規(guī)范里提供了一個(gè)叫BundleActivator的接口,里邊有start和stop兩個(gè)方法,顧名思義,在Bundle啟動(dòng)和停止的時(shí)候會(huì)回調(diào)這兩個(gè)方法,這就好辦了,我們可以在start方法中實(shí)現(xiàn)啟動(dòng)監(jiān)控客戶端的代碼。

  1. <instructions> 
  2. <Bundle-Activator>com.wangyin.sgm.client.Activator</Bundle-Activator> 
  3. <Import-Package>org.osgi.framework,org.slf4j</Import-Package> 
  4. <Export-Package>com.wangyin.sgm.client</Export-Package> 
  5. <Private-Package>com.wangyin.*,com.lmax.disruptor.*,org.apache.flume.* 
  6.         ,org.apache.avro.*,com.thoughtworks.*, 
  7.         ,org.apache.commons.compress.*,org.apache.commons.lang.* 
  8.         ,org.codehaus.jackson.*,org.jboss.netty.*,org.apache.velocity.* 
  9.         ,org.xerial.snappy,org.tukaani.xz.* 
  10. </Private-Package> 
  11. <Bundle-RequiredExecutionEnvironment>JavaSE-1.6</Bundle-RequiredExecutionEnvironment> 
  12. </instructions>

上邊的Bundle-Activator這個(gè)標(biāo)簽,指定這個(gè)Bundle的Activator是哪個(gè)類。

至此所有的問題都得到了解決,這次OSGI應(yīng)用接入APM監(jiān)控足足花掉了兩天的時(shí)間。

由于負(fù)責(zé)監(jiān)控系統(tǒng)的人員并沒有使用過OSGI,被監(jiān)控目標(biāo)系統(tǒng)的開發(fā)人員也不知道監(jiān)控的原理是什么,一開始我們都以兩個(gè)產(chǎn)品整體去接入,做了許多的無用功,耽誤了很多時(shí)間。

從這個(gè)案例可以看出,當(dāng)你所做的東西需要應(yīng)用到一個(gè)你不熟悉的技術(shù)領(lǐng)域的時(shí)候,又不可能有足夠的時(shí)間去學(xué)習(xí)這個(gè)領(lǐng)域的知識(shí),有一個(gè)最好的辦法就是改造你所做的系統(tǒng),從零開始,逐漸加碼,去適應(yīng)那個(gè)不熟悉的領(lǐng)域。

千萬不要想著能整體一下解決所有問題,因?yàn)榭赡芤鉀Q所有問題有100個(gè)技術(shù)點(diǎn)需要去修改,這100個(gè)問題同時(shí)暴露,你只有把它們同時(shí)都修改了,才能看到你的成果,這是根本不可能的事情。

而這100個(gè)問題一個(gè)個(gè)暴露出來,把一個(gè)不可能完成的大任務(wù)拆分成若干個(gè)可完成的小任務(wù),修改一個(gè),看到一步成功的效果,問題就得到了解決。

作者簡(jiǎn)介:

[[215901]]

張晨, 資深研發(fā)工程師,目前任職京東金融,曾任職于搜狐等互聯(lián)網(wǎng)公司,擅長Java底層技術(shù)的研發(fā)及疑難問題的定位。從2015年開始從事智能運(yùn)維監(jiān)控平臺(tái)的研發(fā)與實(shí)踐,參與并主導(dǎo)了APM等產(chǎn)品的研發(fā)與應(yīng)用,經(jīng)歷了多次618和雙11的千萬級(jí)TPS的運(yùn)維保障,支撐了京東金融的大量業(yè)務(wù)應(yīng)用。

[[215902]]

沈建林,曾在多家知名第三方支付公司任職系統(tǒng)架構(gòu)師,致力于基礎(chǔ)中間件與支付核心平臺(tái)的研發(fā),主導(dǎo)過 RPC 服務(wù)框架、數(shù)據(jù)庫分庫分表、統(tǒng)一日志平臺(tái),分布式服務(wù)跟蹤、流程編排等一系列中間件的設(shè)計(jì)與研發(fā),參與過多家支付公司支付核心系統(tǒng)的建設(shè)?,F(xiàn)任京東金融集團(tuán)資深架構(gòu)師,負(fù)責(zé)基礎(chǔ)開發(fā)部基礎(chǔ)中間件的設(shè)計(jì)和研發(fā)工作。擅長基礎(chǔ)中間件設(shè)計(jì)與開發(fā),關(guān)注大型分布式系統(tǒng)、JVM 原理及調(diào)優(yōu)、服務(wù)治理與監(jiān)控等領(lǐng)域。

責(zé)任編輯:王雪燕 來源: 51CTO
相關(guān)推薦

2009-07-29 09:07:51

Linux驅(qū)動(dòng)開源操作系統(tǒng)微軟

2020-02-12 16:50:32

MySQL備份數(shù)據(jù)庫

2020-08-06 17:16:47

抖音Tiktok美國

2010-07-05 09:41:30

美國云計(jì)算

2018-05-26 23:03:07

中興芯片特朗普

2021-10-29 05:39:46

歐盟英偉達(dá)收購

2020-07-14 13:17:23

GitHub宕機(jī)服務(wù)中斷

2021-01-01 09:03:44

故障HAProxy服務(wù)器

2021-11-04 18:27:02

緩存架構(gòu)Eureka

2014-09-02 10:19:22

IT程序員

2014-09-29 14:35:57

WIFI物聯(lián)網(wǎng)RFID

2021-09-01 13:46:07

GitHub Copi漏洞代碼訓(xùn)練

2021-12-26 00:13:24

Log4jLogback漏洞

2010-10-21 14:38:07

網(wǎng)絡(luò)融合

2014-08-19 09:34:01

2015-11-17 12:56:33

浪潮SC15

2010-01-21 17:05:21

互聯(lián)網(wǎng)

2018-05-26 15:50:15

2023-11-16 14:00:23

iOS 17.2蘋果

2013-12-03 10:04:04

Windows更新代號(hào)Windows 8.1
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)