前言
? ? ? 所謂熱部署,就是在應(yīng)用正在運(yùn)行時(shí)升級(jí)軟件,卻不需要重新啟動(dòng)應(yīng)用。對(duì)于 Java應(yīng)用程序來(lái)說(shuō),熱部署就是在運(yùn)行時(shí)更新 Java 類文件,同時(shí)觸發(fā) Spring 以及其他常用第三方框架的一系列重新加載的過(guò)程。而在開發(fā)的過(guò)程中每天重啟服務(wù)5-12次,單次3-8分鐘,每天部署3-5次,部署頻繁耗時(shí)長(zhǎng),嚴(yán)重影響上線的效率。而插件提供的本地和遠(yuǎn)程熱部署功能,可讓將代碼變更“秒級(jí)”生效。

優(yōu)勢(shì)
? ? ? 在使用熱部署插件之后,開發(fā)者修改代碼遠(yuǎn)程熱部署能夠秒級(jí)(2~10s)生效,開發(fā)者直接發(fā)起服務(wù)調(diào)用,可以節(jié)省大量的碎片化時(shí)間(熱部署插件還具備流量回放、遠(yuǎn)程調(diào)用、遠(yuǎn)程反編譯等功能,可配合進(jìn)行使用)。

熱部署的難點(diǎn)
? ? ? 因?yàn)闊岵渴鸩坏韧跓嶂貑?,?Tomcat 或者 Spring Boot DevTools 此類熱重啟模式需要重新加載項(xiàng)目,性能較差。增量熱部署難度較大,需要兼容常用的中間件版本,需要深入啟動(dòng)銷毀加載流程。另外需遵循四大原則。

熱部署的難點(diǎn):
1、可參照資料匱乏
2、字節(jié)碼操作難度大
3、HOTSWAP限制
4、Spring源碼復(fù)雜
5、兼容框架多
6、部署方式多
7、環(huán)境打通難
8、用戶問題調(diào)試難
設(shè)計(jì)方案
架構(gòu)設(shè)計(jì)
? ? ? 插件由 4 大部分組成,包括腳本端、插件端、Agent 端,以及 服務(wù)端。腳本端負(fù)責(zé)自動(dòng)化構(gòu)建 Sonic 啟動(dòng)參數(shù)、服務(wù)啟動(dòng)等集成工作;IDEA 插件端集成環(huán)境為開發(fā)者提供更便捷的熱部署服務(wù);Agent 端隨項(xiàng)目啟動(dòng)負(fù)責(zé)熱部署的功能實(shí)現(xiàn);服務(wù)端則負(fù)責(zé)收集熱部署信息、失敗上報(bào)等統(tǒng)計(jì)工作。如下圖所示:

功能流轉(zhuǎn)
? ? 組件是通過(guò) NIO 監(jiān)聽本地文件變更,觸發(fā)文件變更事件,例如 Class 新增、Class修改、Spring Bean 重載等事件流程。下圖展示了一次熱部署單個(gè)文件的生命周期:

文件監(jiān)聽
? ? ? 首先會(huì)在本地和遠(yuǎn)程預(yù)定義兩個(gè)目錄,/var/tmp/sonic/extraClass?path 和 /var/tmp/sonic/classes。extraClasspath 為 Sonic 自 定 義 的 拓 展Classpath URL,classes 為 Sonic 監(jiān)聽的目錄,當(dāng)有文件變更時(shí),通過(guò) IDEA 插件來(lái)部署到遠(yuǎn)程 / 本地,觸發(fā) Agent 的監(jiān)聽目錄,來(lái)繼續(xù)下面的熱加載邏輯:

注意:
? ? ? 因?yàn)榭紤]到業(yè)務(wù)方WAR 包的 API 項(xiàng)目、Spring Boot、Tomcat 項(xiàng)目、Jetty 項(xiàng)目等,都是以 JAR 包來(lái)啟動(dòng)的,這樣是無(wú)法直接修改用戶的 Class 文件的。即使是用戶項(xiàng)目可以修改,直接操作用戶的 Class,也會(huì)帶來(lái)一系列的安全問題。所以,Sonic 采用拓展 ClassPath URL 路徑來(lái)實(shí)現(xiàn)文件的修改和新增。
? ? ? ? 并且存在這么一種場(chǎng)景,多個(gè)業(yè)務(wù)側(cè)的項(xiàng)目引入相同的 JAR 包,在 JAR 里面配置 MyBatis 的XML 和注解。在此類情況下,Sonic 沒有辦法直接來(lái)修改 JAR 包中源文件,通過(guò)拓展路徑的方式可以不需要關(guān)注 JAR 包,來(lái)修改 JAR 包中某一文件和 XML。同理,采用此類方法可以進(jìn)行整個(gè) JAR 包的熱替換。

JVM Class重載
? ? ? ? JVM 的字節(jié)碼批量重載邏輯,通過(guò)新的字節(jié)碼二進(jìn)制流和舊的 Class 對(duì)象生成ClassDefinition 定 義,instrumentation.redefineClasses(definitions), 來(lái) 觸 發(fā)JVM 重載,重載過(guò)后將觸發(fā)初始化時(shí) Spring 插件注冊(cè)的 Transfrom。
? ? ? ? 新增 class Sonic 如何保證可以加載到 Classloader 上下文中?由于項(xiàng)目在遠(yuǎn)程執(zhí)行,所以運(yùn)行環(huán)境復(fù)雜,有可能是 JAR 包方式啟動(dòng)(Spring Boot),也有可能是普通項(xiàng)目,也有可能是 War Web 項(xiàng)目,針對(duì)此類情況 Sonic 做了一層 ClassloaderURL 拓展。
Spring Bean 重載

Spring XML重載
? ? ? 當(dāng)用戶修改 / 新增 Spring XML 時(shí),需要對(duì) XML 中所有 Bean 進(jìn)行重載
? ? ? 重新 Reload 之后,將 Spring 銷毀后重啟。需要注意的是:XML 修改方式改動(dòng)較大,可能涉及到全局的 AOP 的配置以及前置和后置處理器相關(guān)的內(nèi)容,影響范圍為全局,所以目前只放開普通的 XML Bean 標(biāo)簽的新增 / 修改,其他能力酌情逐步放開。
MyBatis 熱部署
? ? ? Spring MyBatis 熱部署的主要處理流程是在啟動(dòng)期間獲取所有 Configuration 路徑,并維護(hù)它和 Spring Context 的對(duì)應(yīng)關(guān)系,在熱部署 Class、XML 時(shí)去匹配Configuration,從而重新加載 Configuration 以達(dá)到熱部署的目的。


總結(jié)
? ? ? 當(dāng)前熱部署主要是注重Spring Bean、Spring MVC、MyBatis 的重載流程。對(duì)于其他開發(fā)框架,需要更加深入了解底層實(shí)現(xiàn),以達(dá)到兼容性。
補(bǔ)充說(shuō)明
? ? ? 此文只做博主了解熱部署相關(guān)文檔做的簡(jiǎn)單說(shuō)明,該插件實(shí)現(xiàn)難度非常大。