一、概述
Sermant 是基于 Java 字節(jié)碼增強(qiáng)技術(shù)的無代理服務(wù)網(wǎng)格,其利用 Java 字節(jié)碼增強(qiáng)技術(shù),為宿主應(yīng)用程序提供服務(wù)治理功能,以解決大規(guī)模微服務(wù)場景中的服務(wù)治理問題,通過 Java 字節(jié)碼增強(qiáng)技術(shù),可以非侵入的提供服務(wù)治理能力。在以往版本中,Sermant 通過配置 - javaagent 指令在微服務(wù)啟動時接入服務(wù)治理能力,當(dāng)需要接入及卸載 Sermant 時都需要通過重新啟動微服務(wù)來完成。但從 1.2.0 版本開始,Sermant 實(shí)現(xiàn)了在服務(wù)不停機(jī)狀態(tài)下進(jìn)行安裝和卸載的能力,為服務(wù)治理能力帶來全新接入體驗(yàn)。本文將會對這種動態(tài)接入的機(jī)制,從技術(shù)基礎(chǔ)到 Sermant 設(shè)計(jì)進(jìn)行一次深入分析。
二、JavaAgent 加載方式
首先介紹一下 JavaAgent 的不同接入方式,這是 Sermant 實(shí)現(xiàn)動態(tài)接入能力的技術(shù)基礎(chǔ)。Java 中 Instrumentation API 提供了一種修改字節(jié)碼的機(jī)制,利用該 API,可以通過修改字節(jié)碼的方式來改變程序的行為,而不用觸及程序的源碼。JavaAgent 為 Instrumentation API 的客戶端,通過 JavaAgent 可以調(diào)用 API 進(jìn)行字節(jié)碼的操作,其提供了兩種加載方式給開發(fā)者重載:
靜態(tài)加載:利用 premain,在應(yīng)用程序啟動時加載 JavaAgent 稱為靜態(tài)加載,靜態(tài)加載會在啟動時在執(zhí)行任何代碼之前修改字節(jié)碼。
靜態(tài)加載時,字節(jié)碼增強(qiáng)是在類加載時發(fā)生的,當(dāng) Java 程序啟動時,類加載過程中所有被加載的類都會經(jīng)過 JavaAgent 所定義的類文件轉(zhuǎn)換器的處理。
動態(tài)加載:利用 agentmain 通過 Java Attach API 將 JavaAgent 加載到已運(yùn)行的 JVM 中,動態(tài)加載可以通過字節(jié)碼重轉(zhuǎn)換的方式在運(yùn)行時修改字節(jié)碼。
動態(tài)加載時,和靜態(tài)加載不同的是,此時 JVM 已在運(yùn)行,目標(biāo)類已被加載,就不能像靜態(tài)加載時一樣觸發(fā)字節(jié)碼增強(qiáng)過程,在使用動態(tài)加載的過程中,往。往會通過 Instrumentation API 來觸發(fā)目標(biāo)類(當(dāng)然也可以指定所有已被加載的類)的重轉(zhuǎn)換過程,在重轉(zhuǎn)換過程中就會觸發(fā)到 Agent 構(gòu)建的類文件轉(zhuǎn)換器,從而完成字節(jié)碼增強(qiáng)過程。
動態(tài)加載方式為 JavaAgent 提供了在 JVM 運(yùn)行時接入的能力,但通過類重轉(zhuǎn)換來觸發(fā)字節(jié)碼增強(qiáng)相對于在類加載時增強(qiáng)有一定的局限性,例如不能在增強(qiáng)時修改類的繼承關(guān)系,不能為類添加靜態(tài)代碼塊,不能增強(qiáng)內(nèi)存中和資源文件中字節(jié)碼不一致的類等,這些也是在使用動態(tài)加載和多 JavaAgent 場景中常見的問題,綜上,兩種加載方式各有利弊,可以在使用時按照業(yè)務(wù)場景選擇。
三、Sermant 熱插拔能力關(guān)鍵問題剖析
在了解技術(shù)基礎(chǔ)后,我們能輕易的想到,理論上基于 JavaAgent 的動態(tài)加載方式,只需要在使用 Sermant 時,將通過 premain 方式啟動改為通過 agentmain 方式啟動,就可以將微服務(wù)治理能力動態(tài)的接入到微服務(wù)中,做到微服務(wù)零侵入、微服務(wù)不停機(jī)的狀態(tài)下接入服務(wù)治理能力,但通往前方的路上總是充滿了障礙:
3.1 如何保證動態(tài)安裝過程中重轉(zhuǎn)換可順利執(zhí)行?
這個問題的出現(xiàn),根源在于 JavaAgent 通過 agentmain 方式加載到已運(yùn)行的 JVM 中時,不同于靜態(tài)加載,會在類初次被加載時完成字節(jié)碼的轉(zhuǎn)換,動態(tài)加載時一些需要被字節(jié)碼增強(qiáng)類已經(jīng)完成了類加載過程,這時候需要使用 Instrumentation 提供的類重轉(zhuǎn)換(retransform classes)能力來修改字節(jié)碼,在 Instrumentation 的 Javadoc 中關(guān)于這個能力有這樣一段描述:
“The retransformation must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance.(重轉(zhuǎn)換過程中,我們不能新增、刪除或者重命名字段和方法,不能更改方法的簽名,不能更改類的繼承。)”
從中可以看出,在引入動態(tài)加載能力前,優(yōu)先要保證字節(jié)碼增強(qiáng)時,不可以有上述內(nèi)容中所描述的限制操作。
不過 Sermant 不太需要擔(dān)心這個問題,因?yàn)檫@種限制不僅僅在動態(tài)加載時會觸發(fā),在多個 JavaAgent 同時使用時也可能會觸發(fā),可以參考 Sermant 團(tuán)隊(duì)的另一篇文章:《記一次多個 JavaAgent 同時使用的類增強(qiáng)沖突問題及分析》。為了保證在多 Agent 場景下的兼容性,Sermant 的字節(jié)碼增強(qiáng)模板嚴(yán)格遵循 Instrumentation API 的限制,因此 Sermant 在兼容性上的不斷改進(jìn)過程中無心插柳,幫助動態(tài)加載能力鋪平了路。
3.2 如何保證在服務(wù)治理插件安裝和卸載時不互相影響?
Sermant 的設(shè)計(jì)中,通過字節(jié)碼增強(qiáng)引入的服務(wù)治理能力,是通過在目標(biāo)方法上添加服務(wù)治理功能切面來完成的,每一個服務(wù)治理插件,通過一系列切面的配合來達(dá)成最終的服務(wù)治理效果。不同的服務(wù)治理功能,可能會對同一個目標(biāo)方法進(jìn)行處理。但并不會對同一個方法進(jìn)行多次字節(jié)碼增強(qiáng),而是通過一次字節(jié)碼增強(qiáng)織入調(diào)度切面(onMethodEnter、onMethodExit 等),通過該切面對相關(guān)的服務(wù)治理能力(通過攔截器實(shí)現(xiàn),每一個切面會對應(yīng)一個攔截器的列表)進(jìn)行調(diào)度:
對于服務(wù)治理能力的調(diào)度邏輯我們在另一篇文章《開發(fā)者能力機(jī)制解析,玩轉(zhuǎn) Sermant 開發(fā)》有講過,本篇不再贅述。
基于框架的基本設(shè)計(jì),就需要考慮兩個問題,當(dāng)插件在動態(tài)安裝時,如何保證不重復(fù)字節(jié)碼增強(qiáng)?當(dāng)插件卸載時,如何保證不會導(dǎo)致有相同目標(biāo)方法的插件失效。
安裝時如何保證不重復(fù)執(zhí)行字節(jié)碼增強(qiáng)?
在字節(jié)碼增強(qiáng)開發(fā)過程中,類文件轉(zhuǎn)換器(ClassFileTransformer)是一定會接觸到的概念,開發(fā)者需要基于該轉(zhuǎn)換器來進(jìn)行字節(jié)碼的處理。在大多數(shù)的字節(jié)碼增強(qiáng)框架中,都會對其進(jìn)行封裝,用于降低字節(jié)碼處理的難度。Sermant 基于 ByteBuddy 提供的類文件轉(zhuǎn)換器實(shí)現(xiàn)了一種可重入的類轉(zhuǎn)換器,在插件動態(tài)安裝時,雖然目標(biāo)方法已經(jīng)被已安裝的插件增強(qiáng)過了,但此時還是會觸發(fā)類文件轉(zhuǎn)換(因?yàn)閯討B(tài)安裝插件的過程是獨(dú)立的),當(dāng)觸發(fā)類文件轉(zhuǎn)換時,所有相關(guān)的類文件轉(zhuǎn)換器都會被喚醒,再次觸發(fā)類文件轉(zhuǎn)換過程。每次可重入類轉(zhuǎn)換器被喚醒時,將發(fā)生以下行為:
在 Sermant 中維護(hù)了一個針對目標(biāo)方法的字節(jié)碼增強(qiáng)鎖(AdviceKey 鎖),即針對每一個目標(biāo)方法,維護(hù)了 1 個信號量當(dāng)做鎖,用于讓各類文件轉(zhuǎn)換器來檢查目標(biāo)方法的字節(jié)碼增強(qiáng)狀態(tài),當(dāng)目標(biāo)方法對應(yīng)的類被類轉(zhuǎn)換時,就會觸發(fā) Sermant 所提供的類文件轉(zhuǎn)換器,此時類文件轉(zhuǎn)換器將嘗試獲取針對目標(biāo)方法的信號量,如果能獲取信號量,則執(zhí)行對目標(biāo)方法的字節(jié)碼增強(qiáng),如果不能獲取,則不執(zhí)行字節(jié)碼增強(qiáng)。
基于字節(jié)碼增強(qiáng)鎖,在轉(zhuǎn)換器觸發(fā)時,主要有兩條路徑可以走,類文件轉(zhuǎn)換器會通過目標(biāo)方法的 AdviceKey(類名 + 方法 hash+ 類加載器組成的一個唯一表示,用于表示字節(jié)碼增強(qiáng)的目標(biāo)) 來檢查其所關(guān)聯(lián)的鎖,判斷當(dāng)前目標(biāo)方法是否已被 Sermant 進(jìn)行過字節(jié)碼增強(qiáng)(織入攔截器調(diào)度的切面):
1.能獲取鎖,說明未被增強(qiáng):則當(dāng)前文件轉(zhuǎn)換器獲取當(dāng)前 AdviceKey 所關(guān)聯(lián)的鎖,將其獲取的鎖通過其對應(yīng)的插件來維護(hù),并且執(zhí)行字節(jié)碼增強(qiáng),將服務(wù)治理所需的攔截器放入該 AdviceKey 所對應(yīng)的攔截器列表;
2.不能獲取鎖,說明已被增強(qiáng):則只將攔截器放入該 AdviceKey 對應(yīng)的攔截器列表中,不執(zhí)行字節(jié)碼增強(qiáng)。
通過上述機(jī)制,就可以保證 Sermant 在安裝不同服務(wù)治理插件時,不會進(jìn)行重復(fù)的字節(jié)碼增強(qiáng),避免無端的性能和資源損耗。
卸載時如何保證不會導(dǎo)致其他插件失效?
當(dāng)插件需要卸載時,會再次觸發(fā)相關(guān)目標(biāo)類的重轉(zhuǎn)換,與安裝時不同的是,這次需要被卸載的插件釋放自身已經(jīng)持有的 AdviceKey 鎖。釋放鎖后,觸發(fā)目標(biāo)類重轉(zhuǎn)換時,目標(biāo)類所對應(yīng)的各個插件的類文件轉(zhuǎn)換器將會再次觸發(fā)和安裝時相同的流程:
在這個過程中,未被卸載的插件所提供的對目標(biāo)類的類文件轉(zhuǎn)換器,會在目標(biāo)類重轉(zhuǎn)換時,再次觸發(fā),并且只會經(jīng)歷獲取鎖和字節(jié)碼增強(qiáng)的過程。這樣就保證,如果還有插件需要對該目標(biāo)方法進(jìn)行字節(jié)碼增強(qiáng)時,可以獲得目標(biāo)方法所對應(yīng)的鎖,不會因?yàn)槟繕?biāo)方法的交集而導(dǎo)致其他插件能力失效。
審核編輯 黃宇
-
轉(zhuǎn)換器
+關(guān)注
關(guān)注
27文章
9066瀏覽量
151865 -
熱插拔
+關(guān)注
關(guān)注
2文章
251瀏覽量
38517 -
JAVA
+關(guān)注
關(guān)注
20文章
2989瀏覽量
109838
發(fā)布評論請先 登錄
NI發(fā)布全新PXI機(jī)箱,全面提高系統(tǒng)正常運(yùn)行時間
如何縮短Vivado的運(yùn)行時間

基于PCIe-Native機(jī)制的熱插拔
如何檢查Linux服務(wù)器的運(yùn)行時間

熱插拔是什么?熱插拔有哪些特點(diǎn)?
即插即用和熱插拔的區(qū)別
通過測試的2V輸出60A熱插拔控制器完整設(shè)計(jì)
采用1.4 代NexFET的12V輸出60A熱插拔控制器參考設(shè)計(jì)
紫金橋組態(tài)軟件新的功能_運(yùn)行時組態(tài)

評論