您好,登錄后才能下訂單哦!
這篇文章主要介紹了Classloader隔離技術(shù)在業(yè)務(wù)監(jiān)控中怎么應(yīng)用的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Classloader隔離技術(shù)在業(yè)務(wù)監(jiān)控中怎么應(yīng)用文章都會(huì)有所收獲,下面我們一起來看看吧。
業(yè)務(wù)監(jiān)控平臺(tái)是得物自研的一款用于數(shù)據(jù)和狀態(tài)驗(yàn)證的平臺(tái)。能快速便捷發(fā)現(xiàn)線上業(yè)務(wù)臟數(shù)據(jù)和錯(cuò)誤邏輯,有效防止資產(chǎn)損失和保證系統(tǒng)穩(wěn)定性。
數(shù)據(jù)流向:
上圖的過濾和校驗(yàn)步驟的實(shí)際工作就是執(zhí)行一個(gè)用戶自定義的Groovy核對(duì)腳本。業(yè)務(wù)監(jiān)控內(nèi)部通過一個(gè)執(zhí)行腳本的模塊來實(shí)現(xiàn)。
業(yè)務(wù)監(jiān)控核心執(zhí)行邏輯是數(shù)據(jù)校驗(yàn)核對(duì)。不同域會(huì)有不同的數(shù)據(jù)校驗(yàn)核對(duì)規(guī)則。最初版本用戶編寫一個(gè)腳本進(jìn)行調(diào)試的步驟如下:
1.編寫數(shù)據(jù)校驗(yàn)?zāi)_本(在業(yè)務(wù)監(jiān)控平臺(tái)規(guī)則下),腳本demo:
@Service public class DubboDemoScript implements DemoScript { @Resource private DemoService demoService; @Override public boolean filter(JSONObject jsonObject) { // 這里省略數(shù)據(jù)過濾邏輯 由業(yè)務(wù)使用方實(shí)現(xiàn) return true; } @Override public String check(JSONObject jsonObject) { Long id = jsonObject.getLong("id"); // 數(shù)據(jù)校驗(yàn),由業(yè)務(wù)使用方實(shí)現(xiàn) Response responseResult = demoService.queryById(id); log.info("[DubboClassloaderTestDemo]返回結(jié)果={}", JsonUtils.serialize(responseResult)); return JsonUtils.serialize(responseResult); } }
其中DemoScript是業(yè)務(wù)監(jiān)控平臺(tái)定義的一個(gè)模板interface, 不同腳本實(shí)現(xiàn)此接口并重寫 filter和check兩個(gè)方法。filter方法是用來進(jìn)行數(shù)據(jù)過濾的,check方法是進(jìn)行數(shù)據(jù)核對(duì)校驗(yàn)的-用戶主要編寫這兩個(gè)方法中的邏輯。
2.在業(yè)務(wù)監(jiān)控平臺(tái)腳本調(diào)試頁面進(jìn)行調(diào)試腳本,當(dāng)腳本中有第三方團(tuán)隊(duì)Maven依賴時(shí)候,業(yè)務(wù)監(jiān)控平臺(tái)需要在pom.xml中添加Maven依賴并進(jìn)行發(fā)布,之后通知用戶再此進(jìn)行調(diào)試。
3.點(diǎn)擊腳本調(diào)試,查看腳本調(diào)試結(jié)果。
4.保存并上線腳本。
用戶想要調(diào)試一個(gè)腳本需要告知平臺(tái)開發(fā),平臺(tái)開發(fā)手動(dòng)將Maven依賴添加到project中并去發(fā)布平臺(tái)進(jìn)行發(fā)布。中間不僅特別耗時(shí),效率低,而且還要頻繁發(fā)布,嚴(yán)重影響了業(yè)務(wù)監(jiān)控平臺(tái)的用戶使用體驗(yàn)且增加平臺(tái)開發(fā)的維護(hù)成本。
為此,業(yè)務(wù)監(jiān)控平臺(tái)在新版本中使用了Classloader隔離技術(shù)來動(dòng)態(tài)加載腳本中依賴的業(yè)務(wù)方服務(wù)。業(yè)務(wù)監(jiān)控不需要再進(jìn)行特殊處理(添加Maven依賴再進(jìn)行發(fā)布),用戶在管控后臺(tái)直接上傳腳本以來的JAR文件就可以完成調(diào)試,大大降低了使用和維護(hù)成本,提高用戶體驗(yàn)。
ClassLoader是一個(gè)抽象類,我們用它的實(shí)例對(duì)象來裝載類 ,它負(fù)責(zé)將Java字節(jié)碼裝載到JVM中 , 并使其成為JVM一部分。JVM的類動(dòng)態(tài)加載技術(shù)能夠在運(yùn)行時(shí)刻動(dòng)態(tài)地加載或者替換系統(tǒng)的某些功能模塊,而不影響系統(tǒng)其他功能模塊的正常運(yùn)行。一般是通過類名讀入一個(gè)class文件來裝載這個(gè)類。
類裝載就是尋找一個(gè)類或是一個(gè)接口的字節(jié)碼文件并通過解析該字節(jié)碼來構(gòu)造代表這個(gè)類或是這個(gè)接口的class對(duì)象的過程 。在Java中,類裝載器把一個(gè)類裝入Java虛擬機(jī)中,要經(jīng)過三個(gè)步驟來完成:裝載、鏈接和初始化。
利用Classloader實(shí)現(xiàn)類URLClassloader來實(shí)現(xiàn)依賴文件的動(dòng)態(tài)加載。示例代碼:
public class CustomClassLoader extends URLClassLoader { /** * @param jarPath jar文件目錄地址 * @return */ private CustomClassLoader createCustomClassloader(String jarPath) throws MalformedURLException { File file = new File(jarPath); URL url = file.toURI().toURL(); List<URL> urlList = Lists.newArrayList(url); URL[] urls = new URL[urlList.size()]; urls = urlList.toArray(urls); return new CustomJarClassLoader(urls, classLoader.getParent()); } public CustomClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); }
在新增依賴文件的時(shí)候,使用Classloader的addURL方法動(dòng)態(tài)添加來進(jìn)行實(shí)現(xiàn)。
如果所有腳本使用同一個(gè)類加載器,來進(jìn)行加載,就會(huì)出現(xiàn)問題,原因:同一個(gè)類(全限定名一樣)只會(huì)被類加載器加載一次(雙親委派)。但是不同腳本存在兩個(gè)全限定名一樣的情況,但是方法或者屬性不相同,因此加載一次就會(huì)導(dǎo)致其中一個(gè)腳本核對(duì)邏輯出錯(cuò)。
在理解了上面的情況下,我們就需要打破Java雙親委派機(jī)制,這里要知道一個(gè)知識(shí)點(diǎn):一個(gè)類的全限定名以及加載該類的加載器兩者共同形成了這個(gè)類在JVM中的唯一標(biāo)識(shí),因此就需要自定義類加載器,讓腳本和Classloader一一對(duì)應(yīng)且各不相同。話不多說,直接上干貨:
public class CustomClassLoader extends URLClassLoader { public JarFile jarFile; public ClassLoader parent; public CustomClassLoader(URL[] urls, JarFile jarFile, ClassLoader parent) { super(urls, parent); this.jarFile = jarFile; this.parent = parent; } public CustomClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } private static String classNameToJarEntry(String name) { String classPath = name.replaceAll("\\.", "\\/"); return new StringBuilder(classPath).append(".class").toString(); } /** * 重寫loadClass方法,按照類包路徑規(guī)則拉進(jìn)行加載Class到j(luò)vm * @param name 類全限定名 * @return * @throws ClassNotFoundException */ @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 這里定義類加載規(guī)則,和findClass方法一起組合打破雙親 if (name.startsWith("com.xx") || name.startsWith("com.yyy")) { return this.findClass(name); } return super.loadClass(name, resolve); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; try { String jarEntryName = classNameToJarEntry(name); if (jarFile == null) { return clazz; } JarEntry jarEntry = jarFile.getJarEntry(jarEntryName); if (jarEntry != null) { InputStream inputStream = jarFile.getInputStream(jarEntry); byte[] bytes = IOUtils.toByteArray(inputStream); clazz = defineClass(name, bytes, 0, bytes.length); } } catch (IOException e) { log.info("Custom classloader load calss {} failed", name) } return clazz; } }
說明:上述自定義類加載器的loadClass和findClass方法一起達(dá)到破壞雙親委派機(jī)制的關(guān)鍵。其中super.loadClass(name, resolve)方法是不符合自定義類加載器規(guī)則的情況下,讓其父加載器(這里的父加載器就是LanuchUrlClassloader)進(jìn)行類加載,自定義類加載器只關(guān)注自己要加載的類,并按照腳本維度進(jìn)行緩存對(duì)應(yīng)的Classloader。
腳本或者調(diào)試腳本過程中和Classloader之間的創(chuàng)建關(guān)系:
一個(gè)腳本對(duì)應(yīng)多個(gè)依賴的JAR文件(JAR文件在腳本調(diào)試頁面上傳到HDFS),一個(gè)腳本對(duì)應(yīng)一個(gè)classloader(并進(jìn)行本地緩存)(完全相同的兩個(gè)類在不同的classloader中加載后兩個(gè)Class對(duì)象是不相等的)。
在上述的操作中,相信大家對(duì)JAR怎么實(shí)現(xiàn)腳本加載的,和腳本中@Resource注解標(biāo)記的屬性DemoService類如何創(chuàng)建Bean和注入到Spring容器比較關(guān)注。貼張流程圖來講解:
流程圖中生成FeignClient對(duì)象的創(chuàng)建源碼:
/** * * @param serverName 服務(wù)名 (@FeignClient主鍵中的name值) * eg:@FeignClient("demo-interfaces") * @param beanName feign對(duì)象名稱 eg: DemoFeignClient * @param targetClass feign的Class對(duì)象 * @param <T> FeignClient主鍵標(biāo)記的Object * @return */ public static <T> T build(String serverName, String beanName, Class<T> targetClass) { return buildClient(serverName, beanName, targetClass); } private static <T> T buildClient(String serverName, String beanName, Class<T> targetClass) { T t = (T) BEAN_CACHE.get(serverName + "-" + beanName); if (Objects.isNull(t)) { FeignClientBuilder.Builder<T> builder = new FeignClientBuilder(applicationContext).forType(targetClass, serverName); t = builder.build(); BEAN_CACHE.put(serverName + "-" + beanName, t); } return t; }
流程圖中生成注冊(cè)Dubbo consumer的源碼:
public void registerDubboBean(Class clazz, String beanName) { // 當(dāng)前應(yīng)用配置 ApplicationConfig application = new ApplicationConfig(); application.setName("demo-service"); // 連接注冊(cè)中心配置 RegistryConfig registry = new RegistryConfig(); registry.setAddress(registryAddress); // ReferenceConfig為重對(duì)象,內(nèi)部封裝了與注冊(cè)中心的連接,以及與服務(wù)提供方的連接 ReferenceConfig reference = new ReferenceConfig<>(); // 此實(shí)例很重,封裝了與注冊(cè)中心的連接以及與提供者的連接,請(qǐng)自行緩存,否則可能造成內(nèi)存和連接泄漏 reference.setApplication(application); reference.setRegistry(registry); // 多個(gè)注冊(cè)中心可以用setRegistries() reference.setInterface(clazz); reference.setVersion("1.0"); // 注意:此代理對(duì)象內(nèi)部封裝了所有通訊細(xì)節(jié),這里用dubbo2.4版本以后提供的緩存類ReferenceConfigCache ReferenceConfigCache cache = ReferenceConfigCache.getCache(); Object dubboBean = cache.get(reference); dubboBeanMap.put(beanName, dubboBean); // 注冊(cè)bean SpringContextUtils.registerBean(beanName, dubboBean); // 注入bean SpringContextUtils.autowireBean(dubboBean); }
以上就是Classloader隔離技術(shù)在業(yè)務(wù)監(jiān)控平臺(tái)的實(shí)際運(yùn)用,當(dāng)然在開發(fā)中也遇到一些問題,下面列舉2個(gè)例子。
問題一: 多個(gè)團(tuán)隊(duì)的Check腳本運(yùn)行在一起,單個(gè)應(yīng)用的Metaspace空間占用會(huì)不會(huì)過大?
答:隨著業(yè)務(wù)的發(fā)展,JAR文件的不斷增多,確實(shí)會(huì)出現(xiàn)元數(shù)據(jù)區(qū)占用過大的情況,這也是做Classloader隔離的原因。在做了這一步之后,為后面進(jìn)行腳本拆分做了鋪墊,比如按照應(yīng)用、團(tuán)隊(duì)等維度單獨(dú)部署應(yīng)用來運(yùn)行其對(duì)應(yīng)check腳本。這樣腳本和業(yè)務(wù)監(jiān)控邏輯上也進(jìn)行了拆分,也會(huì)降低主應(yīng)用的發(fā)布頻率帶來的噪音。
問題二:Classloader隔離實(shí)現(xiàn)上有沒有遇到什么難題?
答:中間遇到了一些問題,就是同一個(gè)全限定名的類,出現(xiàn)了CastException異常,此類問題是最容易出現(xiàn)的,也最容易想到的。
原因:同一個(gè)類被2個(gè)不同的Classloader對(duì)象加載了2次。解決也很簡單,使用同一個(gè)類加載器。
關(guān)于“Classloader隔離技術(shù)在業(yè)務(wù)監(jiān)控中怎么應(yīng)用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Classloader隔離技術(shù)在業(yè)務(wù)監(jiān)控中怎么應(yīng)用”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。