javascript
第四十章:基于SpringBoot Quartz完成定时任务分布式多节点负载持久化
在上一章【第三十九章:基于SpringBoot & Quartz完成定時(shí)任務(wù)分布式單節(jié)點(diǎn)持久化】中我們已經(jīng)完成了任務(wù)的持久化,當(dāng)我們創(chuàng)建一個(gè)任務(wù)時(shí)任務(wù)會(huì)被quartz定時(shí)任務(wù)框架自動(dòng)持久化到數(shù)據(jù)庫(kù),我們采用的是SpringBoot項(xiàng)目托管的dataSource來(lái)完成的數(shù)據(jù)源提供,當(dāng)然也可以使用quartz內(nèi)部配置數(shù)據(jù)源方式,我們的標(biāo)題既然是提到了定時(shí)任務(wù)的分布式多節(jié)點(diǎn),那么怎么才算是多節(jié)點(diǎn)呢?當(dāng)有節(jié)點(diǎn)故障或者手動(dòng)停止運(yùn)行后是否可以自動(dòng)漂移任務(wù)到可用的分布式節(jié)點(diǎn)呢?
本章目標(biāo)
構(gòu)建項(xiàng)目
注意:我們本章項(xiàng)目需要結(jié)合上一章共同完成,有一點(diǎn)要注意的是任務(wù)在持久化到數(shù)據(jù)庫(kù)內(nèi)時(shí)會(huì)保存任務(wù)的全路徑,如:com.hengyu.chapter39.timers.GoodStockCheckTimer ,quartz在運(yùn)行任務(wù)時(shí)會(huì)根據(jù)任務(wù)全路徑去執(zhí)行,如果不一致則會(huì)提示找不到指定類,我們本章在創(chuàng)建項(xiàng)目時(shí)package需要跟上一章完全一致。
我們這里就不去直接創(chuàng)建新項(xiàng)目了,直接復(fù)制上一章項(xiàng)目的源碼為新的項(xiàng)目命名為Chapter40
配置分布式
在上一章配置文件quartz.properties中我們其實(shí)已經(jīng)為分布式做好了相關(guān)配置,下面我們就來(lái)看一下分布式相關(guān)的配置。
分布式相關(guān)配置:
1. org.quartz.scheduler.instanceId : 定時(shí)任務(wù)的實(shí)例編號(hào),如果手動(dòng)指定需要保證每個(gè)節(jié)點(diǎn)的唯一性,因?yàn)閝uartz不允許出現(xiàn)兩個(gè)相同instanceId的節(jié)點(diǎn),我們這里指定為Auto就可以了,我們把生成編號(hào)的任務(wù)交給quartz。
2. org.quartz.jobStore.isClustered: 這個(gè)屬性才是真正的開啟了定時(shí)任務(wù)的分布式配置,當(dāng)我們配置為true時(shí)quartz框架就會(huì)調(diào)用ClusterManager來(lái)初始化分布式節(jié)點(diǎn)。
3. org.quartz.jobStore.clusterCheckinInterval:配置了分布式節(jié)點(diǎn)的檢查時(shí)間間隔,單位:毫秒。
下面是quartz.properties配置文件配置信息:
當(dāng)我們啟動(dòng)任務(wù)節(jié)點(diǎn)時(shí),會(huì)根據(jù)org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread屬性配置進(jìn)行是否自動(dòng)加載任務(wù),默認(rèn)true自動(dòng)加載數(shù)據(jù)庫(kù)內(nèi)的任務(wù)到節(jié)點(diǎn)。
測(cè)試分布式
上一章項(xiàng)目節(jié)點(diǎn)名稱:quartz-cluster-node-first
本章項(xiàng)目節(jié)點(diǎn)名稱:quartz-cluster-node-second
由于我們quartz-cluster-node-first的商品庫(kù)存檢查定時(shí)任務(wù)是每隔30秒執(zhí)行一次,所以任務(wù)除非手動(dòng)清除否則是不會(huì)被清空的,在運(yùn)行項(xiàng)目測(cè)試之前需要將application.yml配置文件的端口號(hào)、項(xiàng)目名稱修改下,保證quartz-cluster-node-second與quartz-cluster-node-first端口號(hào)不一致,可以同時(shí)運(yùn)行,修改后為:
spring:application:name: quzrtz-cluster-node-second server:port: 8082復(fù)制代碼然后修改相應(yīng)控制臺(tái)輸出,為了能夠區(qū)分任務(wù)執(zhí)行者是具體的節(jié)點(diǎn)。
Chapter40Application啟動(dòng)類修改日志輸出: logger.info("【【【【【【定時(shí)任務(wù)分布式節(jié)點(diǎn) - quartz-cluster-node-second 已啟動(dòng)】】】】】】");GoodAddTimer商品添加任務(wù)類修改日志輸出: logger.info("分布式節(jié)點(diǎn)quartz-cluster-node-second,商品添加完成后執(zhí)行任務(wù),任務(wù)時(shí)間:{}",new Date());GoodStockCheckTimer商品庫(kù)存檢查任務(wù)類修改日志輸出: logger.info("分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:{}",new Date());復(fù)制代碼下面我們啟動(dòng)本章項(xiàng)目,查看控制臺(tái)輸出內(nèi)容,如下所示:
2017-11-12 10:28:39.969 INFO 11048 --- [ main] c.hengyu.chapter39.Chapter40Application : 【【【【【【定時(shí)任務(wù)分布式節(jié)點(diǎn) - quartz-cluster-node-second 已啟動(dòng)】】】】】】 2017-11-12 10:28:41.930 INFO 11048 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now, after delay of 2 seconds 2017-11-12 10:28:41.959 INFO 11048 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler : Scheduler schedulerFactoryBean_$_yuqiyu1510453719308 started. 2017-11-12 10:28:51.963 INFO 11048 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: detected 1 failed or restarted instances. 2017-11-12 10:28:51.963 INFO 11048 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: Scanning for instance "yuqiyu1510450938654"'s failed in-progress jobs. 2017-11-12 10:28:51.967 INFO 11048 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: ......Freed 1 acquired trigger(s). 2017-11-12 10:29:00.024 INFO 11048 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 10:29:00 CST 2017復(fù)制代碼可以看到項(xiàng)目啟動(dòng)完成后自動(dòng)分配的instanceId為yuqiyu1510450938654,生成的規(guī)則是當(dāng)前用戶的名稱+時(shí)間戳。然后ClusterManager分布式管理者自動(dòng)介入進(jìn)行掃描是否存在匹配的觸發(fā)器任務(wù),如果存在則會(huì)自動(dòng)執(zhí)行任務(wù)邏輯,而商品庫(kù)存檢查定時(shí)任務(wù)確實(shí)由quartz-cluster-node-second進(jìn)行輸出的。
測(cè)試任務(wù)自動(dòng)漂移
下面我們也需要把quartz-cluster-node-first的輸出進(jìn)行修改,如下所示:
Chapter39Application啟動(dòng)類修改日志輸出: logger.info("【【【【【【定時(shí)任務(wù)分布式節(jié)點(diǎn) - quartz-cluster-node-first 已啟動(dòng)】】】】】】");GoodAddTimer商品添加任務(wù)類修改日志輸出: logger.info("分布式節(jié)點(diǎn)quartz-cluster-node-first,商品添加完成后執(zhí)行任務(wù),任務(wù)時(shí)間:{}",new Date());GoodStockCheckTimer商品庫(kù)存檢查任務(wù)類修改日志輸出: logger.info("分布式節(jié)點(diǎn)quartz-cluster-node-first,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:{}",new Date());復(fù)制代碼接下來(lái)我們啟動(dòng)quartz-cluster-node-first,并查看控制臺(tái)的輸出內(nèi)容:
2017-11-12 10:34:09.750 INFO 192 --- [ main] c.hengyu.chapter39.Chapter39Application : 【【【【【【定時(shí)任務(wù)分布式節(jié)點(diǎn) - quartz-cluster-node-first 已啟動(dòng)】】】】】】 2017-11-12 10:34:11.690 INFO 192 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now, after delay of 2 seconds 2017-11-12 10:34:11.714 INFO 192 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler : Scheduler schedulerFactoryBean_$_yuqiyu1510454049066 started.復(fù)制代碼項(xiàng)目啟動(dòng)完成后,定時(shí)節(jié)點(diǎn)并沒有實(shí)例化ClusterManager來(lái)完成分布式節(jié)點(diǎn)的初始化,因?yàn)閝uartz檢測(cè)到有其他的節(jié)點(diǎn)正在處理任務(wù),這樣也是保證了任務(wù)執(zhí)行的唯一性。
關(guān)閉quartz-cluster-node-second
我們關(guān)閉quartz-cluster-node-second運(yùn)行的項(xiàng)目,預(yù)計(jì)的目的可以達(dá)到quartz-cluster-node-first會(huì)自動(dòng)接管數(shù)據(jù)庫(kù)內(nèi)的任務(wù),完成任務(wù)執(zhí)行的自動(dòng)漂移,我們來(lái)查看quartz-cluster-node-first的控制臺(tái)輸出內(nèi)容:
2017-11-12 10:34:09.750 INFO 192 --- [ main] c.hengyu.chapter39.Chapter39Application : 【【【【【【定時(shí)任務(wù)分布式節(jié)點(diǎn) - quartz-cluster-node-first 已啟動(dòng)】】】】】】 2017-11-12 10:34:11.690 INFO 192 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now, after delay of 2 seconds 2017-11-12 10:34:11.714 INFO 192 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler : Scheduler schedulerFactoryBean_$_yuqiyu1510454049066 started. 2017-11-12 10:41:11.793 INFO 192 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: detected 1 failed or restarted instances. 2017-11-12 10:41:11.793 INFO 192 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: Scanning for instance "yuqiyu1510453719308"'s failed in-progress jobs. 2017-11-12 10:41:11.797 INFO 192 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: ......Freed 1 acquired trigger(s). 2017-11-12 10:41:11.834 INFO 192 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-first,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 10:41:11 CST 2017復(fù)制代碼控制臺(tái)已經(jīng)輸出了持久的定時(shí)任務(wù),輸出節(jié)點(diǎn)是quartz-cluster-node-first,跟我們預(yù)計(jì)的一樣,節(jié)點(diǎn)quartz-cluster-node-first完成了自動(dòng)接管quartz-cluster-node-second的工作,而這個(gè)過(guò)程肯定有一段時(shí)間間隔,而這個(gè)間隔可以修改quartz.properties配置文件內(nèi)的屬性org.quartz.jobStore.clusterCheckinInterval進(jìn)行調(diào)節(jié)。
關(guān)閉quartz-cluster-node-first
我們同樣可以測(cè)試啟動(dòng)任務(wù)節(jié)點(diǎn)quartz-cluster-node-second后,再關(guān)閉quartz-cluster-node-first任務(wù)節(jié)點(diǎn),查看quartz-cluster-node-second控制臺(tái)的輸出內(nèi)容:
2017-11-12 10:53:31.010 INFO 3268 --- [ main] c.hengyu.chapter39.Chapter40Application : 【【【【【【定時(shí)任務(wù)分布式節(jié)點(diǎn) - quartz-cluster-node-second 已啟動(dòng)】】】】】】 2017-11-12 10:53:32.967 INFO 3268 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now, after delay of 2 seconds 2017-11-12 10:53:32.992 INFO 3268 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler : Scheduler schedulerFactoryBean_$_yuqiyu1510455210493 started. 2017-11-12 10:53:52.999 INFO 3268 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: detected 1 failed or restarted instances. 2017-11-12 10:53:52.999 INFO 3268 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: Scanning for instance "yuqiyu1510454049066"'s failed in-progress jobs. 2017-11-12 10:53:53.003 INFO 3268 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore : ClusterManager: ......Freed 1 acquired trigger(s). 2017-11-12 10:54:00.020 INFO 3268 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 10:54:00 CST 2017復(fù)制代碼得到的結(jié)果是同樣可以完成任務(wù)的自動(dòng)漂移。
如果兩個(gè)節(jié)點(diǎn)同時(shí)啟動(dòng),哪個(gè)節(jié)點(diǎn)先把節(jié)點(diǎn)信息注冊(cè)到數(shù)據(jù)庫(kù)就獲得了優(yōu)先執(zhí)行權(quán)。
傳遞參數(shù)到任務(wù)
我們平時(shí)在使用任務(wù)時(shí),如果是針對(duì)性比較強(qiáng)的業(yè)務(wù)邏輯,肯定需要特定的參數(shù)來(lái)完成業(yè)務(wù)邏輯的實(shí)現(xiàn)。
下面我們來(lái)模擬商品秒殺的場(chǎng)景,當(dāng)我們添加商品后自動(dòng)添加一個(gè)商品提前五分鐘的秒殺提醒,為關(guān)注該商品的用戶發(fā)送提醒消息。
我們?cè)诠?jié)點(diǎn)quartz-cluster-node-first中添加秒殺提醒任務(wù),如下所示:
在秒殺提醒任務(wù)邏輯中,我們通過(guò)獲取JobDetail的JobDataMap集合來(lái)獲取在創(chuàng)建任務(wù)的時(shí)候傳遞的參數(shù)集合,我們這里約定了goodId為商品的編號(hào),在創(chuàng)建任務(wù)的時(shí)候傳遞到JobDataMap內(nèi),這樣quartz在執(zhí)行該任務(wù)的時(shí)候就會(huì)自動(dòng)將參數(shù)傳遞到任務(wù)邏輯中,我們也就可以通過(guò)JobDataMap獲取到對(duì)應(yīng)的參數(shù)值。
設(shè)置秒殺提醒任務(wù)
我們找到節(jié)點(diǎn)項(xiàng)目quartz-cluster-node-first中的GoodInfoService,編寫方法buildGoodSecKillRemindTimer設(shè)置秒殺提醒任務(wù),如下所示:
/*** 構(gòu)建商品秒殺提醒定時(shí)任務(wù)* 設(shè)置五分鐘后執(zhí)行* @throws Exception*/public void buildGoodSecKillRemindTimer(Long goodId) throws Exception{//任務(wù)名稱String name = UUID.randomUUID().toString();//任務(wù)所屬分組String group = GoodSecKillRemindTimer.class.getName();//秒殺開始時(shí)間long startTime = System.currentTimeMillis() + 1000 * 5 * 60;JobDetail jobDetail = JobBuilder.newJob(GoodSecKillRemindTimer.class).withIdentity(name,group).build();//設(shè)置任務(wù)傳遞商品編號(hào)參數(shù)jobDetail.getJobDataMap().put("goodId",goodId);//創(chuàng)建任務(wù)觸發(fā)器Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).startAt(new Date(startTime)).build();//將觸發(fā)器與任務(wù)綁定到調(diào)度器內(nèi)scheduler.scheduleJob(jobDetail,trigger);}復(fù)制代碼我們模擬秒殺提醒時(shí)間是添加商品后的5分鐘,我們通過(guò)調(diào)用jobDetail實(shí)例的getJobDataMap方法就可以獲取該任務(wù)數(shù)據(jù)集合,直接調(diào)用put方法就可以進(jìn)行設(shè)置指定key的值,該集合繼承了StringKeyDirtyFlagMap并且實(shí)現(xiàn)了Serializable序列化,因?yàn)樾枰獙?shù)據(jù)序列化到數(shù)據(jù)庫(kù)的qrtz_job_details表內(nèi)。
修改保存商品方法,添加調(diào)用秒殺提醒任務(wù):
添加測(cè)試商品
下面我們調(diào)用節(jié)點(diǎn)quartz-cluster-node-first的測(cè)試Chapter39ApplicationTests.addGood方法完成商品的添加,由于我們的quartz-cluster-node-second項(xiàng)目并沒有停止,所以我們?cè)趒uartz-cluster-node-second項(xiàng)目的控制臺(tái)查看輸出內(nèi)容:
2017-11-12 11:45:00.008 INFO 11652 --- [ryBean_Worker-5] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:45:00 CST 2017 2017-11-12 11:45:30.013 INFO 11652 --- [ryBean_Worker-6] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:45:30 CST 2017 2017-11-12 11:45:48.230 INFO 11652 --- [ryBean_Worker-7] c.hengyu.chapter39.timers.GoodAddTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,商品添加完成后執(zhí)行任務(wù),任務(wù)時(shí)間:Sun Nov 12 11:45:48 CST 2017 2017-11-12 11:46:00.008 INFO 11652 --- [ryBean_Worker-8] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:46:00 CST 2017 2017-11-12 11:46:30.016 INFO 11652 --- [ryBean_Worker-9] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:46:30 CST 2017 2017-11-12 11:47:00.011 INFO 11652 --- [yBean_Worker-10] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:47:00 CST 2017 2017-11-12 11:47:30.028 INFO 11652 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:47:30 CST 2017 2017-11-12 11:48:00.014 INFO 11652 --- [ryBean_Worker-2] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:48:00 CST 2017 2017-11-12 11:48:30.013 INFO 11652 --- [ryBean_Worker-3] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:48:30 CST 2017 2017-11-12 11:49:00.010 INFO 11652 --- [ryBean_Worker-4] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:49:00 CST 2017 2017-11-12 11:49:30.028 INFO 11652 --- [ryBean_Worker-5] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:49:30 CST 2017 2017-11-12 11:49:48.290 INFO 11652 --- [ryBean_Worker-6] c.h.c.timers.GoodSecKillRemindTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,開始處理秒殺商品:15,關(guān)注用戶推送消息. 2017-11-12 11:50:00.008 INFO 11652 --- [ryBean_Worker-7] c.h.c.timers.GoodStockCheckTimer : 分布式節(jié)點(diǎn)quartz-cluster-node-second,執(zhí)行庫(kù)存檢查定時(shí)任務(wù),執(zhí)行時(shí)間:Sun Nov 12 11:50:00 CST 2017復(fù)制代碼秒殺任務(wù)在添加完成商品后的五分鐘開始執(zhí)行的,而我們也正常的輸出了傳遞過(guò)去的goodId商品編號(hào)的參數(shù),而秒殺提醒任務(wù)執(zhí)行一次后也被自動(dòng)釋放了。
總結(jié)
本章主要是結(jié)合上一章完成了分布式任務(wù)的講解,完成了測(cè)試持久化的定時(shí)任務(wù)自動(dòng)漂移,以及如何向定時(shí)任務(wù)傳遞參數(shù)。當(dāng)然在實(shí)際的開發(fā)過(guò)程中,任務(wù)創(chuàng)建是需要進(jìn)行封裝的,目的也是為了減少一些冗余代碼以及方面后期統(tǒng)一維護(hù)定時(shí)任務(wù)。
本章源碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:gitee.com/hengboy/spr…
SpringCloud配套源碼地址:gitee.com/hengboy/spr…
SpringBoot相關(guān)系列文章請(qǐng)?jiān)L問(wèn):目錄:SpringBoot學(xué)習(xí)目錄
QueryDSL相關(guān)系列文章請(qǐng)?jiān)L問(wèn):QueryDSL通用查詢框架學(xué)習(xí)目錄
SpringDataJPA相關(guān)系列文章請(qǐng)?jiān)L問(wèn):目錄:SpringDataJPA學(xué)習(xí)目錄
SpringBoot相關(guān)文章請(qǐng)?jiān)L問(wèn):目錄:SpringBoot學(xué)習(xí)目錄,感謝閱讀!
歡迎加入QQ技術(shù)交流群,共同進(jìn)步。
總結(jié)
以上是生活随笔為你收集整理的第四十章:基于SpringBoot Quartz完成定时任务分布式多节点负载持久化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 安装多个版本JDK相关问题
- 下一篇: 删除mongodb库