全鏈路監(jiān)控熟吏,想說愛你并不容易
當(dāng)系統(tǒng)上線后你沒有這樣的經(jīng)歷?
- 客戶反饋系統(tǒng)慢夫否,你確束手無策
- 系統(tǒng)報(bào)出了異常殖熟,你確分析不出源頭在哪里
- 系統(tǒng)可能面臨突發(fā)流量局待,你確不知道是否能支撐得住
- 問題只在正式重現(xiàn),測試環(huán)境缺無法復(fù)現(xiàn)
- 每次出現(xiàn)問題無法預(yù)警菱属,問題出現(xiàn)問題后事后檢討钳榨,而沒有事前預(yù)警
......
如果你有上面的困擾,說明你的系統(tǒng)缺少可觀測性纽门,缺少一個(gè)全鏈路監(jiān)控系統(tǒng)薛耻。系統(tǒng)越復(fù)雜,我們發(fā)現(xiàn)出現(xiàn)問題時(shí)越來越無法駕馭赏陵,各種問題層出不窮饼齿,問題越來越多,為了掌握系統(tǒng)的運(yùn)行狀態(tài)蝙搔,確保系統(tǒng)正常對(duì)外提供服務(wù)候醒,需要一些手段去監(jiān)控系統(tǒng),以了解系統(tǒng)行為杂瘸,分析系統(tǒng)的性能,或在系統(tǒng)出現(xiàn)故障時(shí)伙菊,能發(fā)現(xiàn)問題败玉、記錄問題并發(fā)出告警敌土,從而幫助運(yùn)維人員發(fā)現(xiàn)問題、定位問題运翼。也可以根據(jù)監(jiān)控?cái)?shù)據(jù)發(fā)現(xiàn)系統(tǒng)瓶頸返干,提前感知故障,預(yù)判系統(tǒng)負(fù)載能力等血淌。
那么我們?nèi)绾稳プ鼍厍罚吭缭?010年谷歌發(fā)表了一篇論文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》介紹了分布式追蹤的概念,之后很多互聯(lián)網(wǎng)公司都開始根據(jù)這篇論文打造自己的分布式鏈路追蹤系統(tǒng)悠夯。前面提到的 APM 系統(tǒng)的核心技術(shù)就是分布式鏈路追蹤癌淮。
為了追蹤錯(cuò)綜復(fù)雜的調(diào)用關(guān)系,我們需要一些手段去輔助完成沦补,最簡單的方式就是畫圖乳蓄,如下圖的場景
客戶端請(qǐng)求經(jīng)過負(fù)載均衡,負(fù)載均衡提供了三種服務(wù)
- RPC
- Web服務(wù)
- 資源管理
上面的方式雖然直觀夕膀,但是隨著系統(tǒng)的復(fù)雜性增加導(dǎo)致我們很清楚的查看調(diào)用關(guān)系虚倒,所以有沒有更好的表達(dá)方式?
上面的方式就是 OpenTracing 規(guī)范推薦給我們的方案产舞。
其實(shí)Tracing只是全鏈路很小的一部分魂奥,我們來看看全貌
Logging 就是記錄系統(tǒng)行為的離散事件,例如易猫,服務(wù)在處理某個(gè)請(qǐng)求時(shí)打印的錯(cuò)誤日志耻煤,我們可以將這些日志信息記錄到 ElasticSearch 或是其他存儲(chǔ)中,然后通過 Kibana 或是其他工具來分析這些日志了解服務(wù)的行為和狀態(tài)擦囊。大多數(shù)情況下违霞,日志記錄的數(shù)據(jù)很分散,并且相互獨(dú)立瞬场,比如錯(cuò)誤日志买鸽、請(qǐng)求處理過程中關(guān)鍵步驟的日志等等。
Metrics 是系統(tǒng)在一段時(shí)間內(nèi)某一方面的某個(gè)度量贯被,例如眼五,電商系統(tǒng)在一分鐘內(nèi)的請(qǐng)求次數(shù)。我們常見的監(jiān)控系統(tǒng)中記錄的數(shù)據(jù)都屬于這個(gè)范疇彤灶,例如 Promethus看幼、Open-Falcon 等,這些監(jiān)控系統(tǒng)最終給運(yùn)維人員展示的是一張張二維的折線圖幌陕。Metrics 是可以聚合的诵姜,例如,為電商系統(tǒng)中每個(gè) HTTP 接口添加一個(gè)計(jì)數(shù)器搏熄,計(jì)算每個(gè)接口的 QPS棚唆,之后我們就可以通過簡單的加和計(jì)算得到系統(tǒng)的總負(fù)載情況暇赤。
Tracing 即我們常說的分布式鏈路追蹤。在微服務(wù)架構(gòu)系統(tǒng)中一個(gè)請(qǐng)求會(huì)經(jīng)過很多服務(wù)處理宵凌,調(diào)用鏈路會(huì)非常長鞋囊,要確定中間哪個(gè)服務(wù)出現(xiàn)異常是非常麻煩的一件事。通過分布式鏈路追蹤瞎惫,運(yùn)維人員就可以構(gòu)建一個(gè)請(qǐng)求的視圖溜腐,這個(gè)視圖上展示了一個(gè)請(qǐng)求從進(jìn)入系統(tǒng)開始到返回響應(yīng)的整個(gè)流程。這樣瓜喇,就可以從中了解到所有服務(wù)的異常情況挺益、網(wǎng)絡(luò)調(diào)用,以及系統(tǒng)的性能瓶頸等欠橘。
更復(fù)雜的是我們要分析日志之間的血緣關(guān)系矩肩,然后根據(jù)大數(shù)據(jù)進(jìn)行計(jì)算和預(yù)警,輔助我們判斷可能要發(fā)生的系統(tǒng)故障事件肃续。
常見的APM系統(tǒng)有
CAT: 由國內(nèi)美團(tuán)點(diǎn)評(píng)開源的黍檩,基于 Java 語言開發(fā),目前提供 Java始锚、C/C++刽酱、Node.js、Python瞧捌、Go 等語言的客戶端棵里,監(jiān)控?cái)?shù)據(jù)會(huì)全量統(tǒng)計(jì)。國內(nèi)很多公司在用姐呐,例如美團(tuán)點(diǎn)評(píng)殿怜、攜程、拼多多等曙砂。CAT 需要開發(fā)人員手動(dòng)在應(yīng)用程序中埋點(diǎn)头谜,對(duì)代碼侵入性比較強(qiáng)。
Zipkin: 由 Twitter 公司開發(fā)并開源鸠澈,Java 語言實(shí)現(xiàn)柱告。侵入性相對(duì)于 CAT 要低一點(diǎn),需要對(duì)web.xml 等相關(guān)配置文件進(jìn)行修改笑陈,但依然對(duì)系統(tǒng)有一定的侵入性际度。Zipkin 可以輕松與 Spring Cloud 進(jìn)行集成,也是 Spring Cloud 推薦的 APM 系統(tǒng)涵妥。
Pinpoint: 韓國團(tuán)隊(duì)開源的 APM 產(chǎn)品乖菱,運(yùn)用了字節(jié)碼增強(qiáng)技術(shù),只需要在啟動(dòng)時(shí)添加啟動(dòng)參數(shù)即可實(shí)現(xiàn) APM 功能,對(duì)代碼無侵入窒所。目前支持 Java 和 PHP 語言娜氏,底層采用 HBase 來存儲(chǔ)數(shù)據(jù),探針收集的數(shù)據(jù)粒度非常細(xì)墩新,但性能損耗較大,因其出現(xiàn)的時(shí)間較長窟坐,完成度也很高海渊,文檔也較為豐富,應(yīng)用的公司較多哲鸳。
SkyWalking: 國人開源的產(chǎn)品臣疑,2019 年 4 月 17 日 SkyWalking 從 Apache 基金會(huì)的孵化器畢業(yè)成為頂級(jí)項(xiàng)目。目前 SkyWalking 支持 Java徙菠、.Net讯沈、Node.js 等探針,數(shù)據(jù)存儲(chǔ)支持MySQL婿奔、ElasticSearch等缺狠。 SkyWalking 與 Pinpoint 相同,Java 探針采用字節(jié)碼增強(qiáng)技術(shù)實(shí)現(xiàn)萍摊,對(duì)業(yè)務(wù)代碼無侵入挤茄。探針采集數(shù)據(jù)粒度相較于 Pinpoint 來說略粗,但性能表現(xiàn)優(yōu)秀冰木。目前穷劈,SkyWalking 增長勢頭強(qiáng)勁,社區(qū)活躍踊沸,中文文檔齊全歇终,沒有語言障礙,支持多語言探針逼龟,這些都是 SkyWalking 的優(yōu)勢所在评凝,還有就是 SkyWalking 支持很多框架,包括很多國產(chǎn)框架审轮,例如肥哎,Dubbo、gRPC疾渣、SOFARPC 等等篡诽,也有很多開發(fā)者正在不斷向社區(qū)提供更多插件以支持更多組件無縫接入 SkyWalking。
還有很多不開源的 APM 系統(tǒng)榴捡,例如杈女,淘寶鷹眼、Google Dapper 等等,不再展開介紹了达椰。
在選擇一款A(yù)PM系統(tǒng)對(duì)系統(tǒng)的侵入是我們需要首要考慮的問題翰蠢,其次是性能問題,所以SkyWalking是我們的最常見選擇啰劲。
那么SkyWalking能為我們做什么梁沧?
服務(wù)、服務(wù)實(shí)例蝇裤、端點(diǎn)指標(biāo)分析廷支。
服務(wù)拓?fù)鋱D分析
服務(wù)、服務(wù)實(shí)例和端點(diǎn)(Endpoint)SLA 分析
慢查詢檢測
告警
SkyWalking的整體架構(gòu)包括三部分
Agent(探針):Agent 運(yùn)行在各個(gè)服務(wù)實(shí)例中栓辜,負(fù)責(zé)采集服務(wù)實(shí)例的 Trace 恋拍、Metrics 等數(shù)據(jù),然后通過 gRPC 方式上報(bào)給 SkyWalking 后端藕甩。
-
OAP:SkyWalking 的后端服務(wù)施敢,其主要責(zé)任有兩個(gè)。
一個(gè)是負(fù)責(zé)接收 Agent 上報(bào)上來的 Trace狭莱、Metrics 等數(shù)據(jù)僵娃,交給 Analysis Core (涉及 SkyWalking OAP 中的多個(gè)模塊)進(jìn)行流式分析,最終將分析得到的結(jié)果寫入持久化存儲(chǔ)中贩毕。SkyWalking 可以使用 ElasticSearch悯许、H2、MySQL 等作為其持久化存儲(chǔ)辉阶,一般線上使用 ElasticSearch 集群作為其后端存儲(chǔ)先壕。
另一個(gè)是負(fù)責(zé)響應(yīng) SkyWalking UI 界面發(fā)送來的查詢請(qǐng)求,將前面持久化的數(shù)據(jù)查詢出來谆甜,組成正確的響應(yīng)結(jié)果返回給 UI 界面進(jìn)行展示垃僚。
UI 界面:SkyWalking 前后端進(jìn)行分離,該 UI 界面負(fù)責(zé)將用戶的查詢操作封裝為 GraphQL 請(qǐng)求提交給 OAP 后端觸發(fā)后續(xù)的查詢操作规辱,待拿到查詢結(jié)果之后會(huì)在前端負(fù)責(zé)展示谆棺。
SkyWalking 源代碼解讀
SkyWalking使用java agent技術(shù)實(shí)現(xiàn),對(duì)于agent如何無侵入實(shí)現(xiàn)日志記錄和監(jiān)控可以看我之前寫得這篇文章,了解了基礎(chǔ)原理之后我們需要搭建調(diào)試環(huán)境來學(xué)習(xí)源代碼罕袋,可以參考這篇進(jìn)行環(huán)境搭建改淑,最后搭建好的項(xiàng)目結(jié)構(gòu)如下圖
上面apm模塊為SkyWalking服務(wù)端,java-agent為客戶端浴讯,另外一個(gè)是演示項(xiàng)目朵夏。
體驗(yàn)微內(nèi)核之美
可以說SkyWalking的服務(wù)端和客戶端都是采用微內(nèi)核設(shè)計(jì),客戶端的入口是SkyWalkingAgent
客戶端代碼解讀
它其實(shí)是一個(gè)java agent的入口類榆纽,需要實(shí)現(xiàn)一個(gè)靜態(tài)的premain方法仰猖,如下
/**
* Main entrance. Use byte-buddy transform to enhance all classes, which define in plugins.
*/
public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
final PluginFinder pluginFinder;
try {
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
} catch (Exception e) {
// try to resolve a new logger, and use the new logger to write the error log here
LogManager.getLogger(SkyWalkingAgent.class)
.error(e, "SkyWalking agent initialized failure. Shutting down.");
return;
} finally {
// refresh logger again after initialization finishes
LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
}
try {
pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
} catch (AgentPackageNotFoundException ape) {
LOGGER.error(ape, "Locate agent.jar failure. Shutting down.");
return;
} catch (Exception e) {
LOGGER.error(e, "SkyWalking agent initialized failure. Shutting down.");
return;
}
final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("org.slf4j."))
.or(nameStartsWith("org.groovy."))
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.or(nameContains(".reflectasm."))
.or(nameStartsWith("sun.reflect"))
.or(allSkyWalkingAgentExcludeToolkit())
.or(ElementMatchers.isSynthetic()));
JDK9ModuleExporter.EdgeClasses edgeClasses = new JDK9ModuleExporter.EdgeClasses();
try {
agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses);
} catch (Exception e) {
LOGGER.error(e, "SkyWalking agent inject bootstrap instrumentation failure. Shutting down.");
return;
}
try {
agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses);
} catch (Exception e) {
LOGGER.error(e, "SkyWalking agent open read edge in JDK 9+ failure. Shutting down.");
return;
}
if (Config.Agent.IS_CACHE_ENHANCED_CLASS) {
try {
agentBuilder = agentBuilder.with(new CacheableTransformerDecorator(Config.Agent.CLASS_CACHE_MODE));
LOGGER.info("SkyWalking agent class cache [{}] activated.", Config.Agent.CLASS_CACHE_MODE);
} catch (Exception e) {
LOGGER.error(e, "SkyWalking agent can't active class cache.");
}
}
agentBuilder.type(pluginFinder.buildMatch())
.transform(new Transformer(pluginFinder))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new RedefinitionListener())
.with(new Listener())
.installOn(instrumentation);
PluginFinder.pluginInitCompleted();
try {
ServiceManager.INSTANCE.boot();
} catch (Exception e) {
LOGGER.error(e, "Skywalking agent boot failure.");
}
Runtime.getRuntime()
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
}
上面代碼做了以下幾件事件
- SnifferConfigInitializer.initializeCoreConfig 用來加載客戶端的配置捏肢,這個(gè)類里面有個(gè)全局靜態(tài)Map,后面其它地方可以通用它來使用配置
- 初始化PluginFinder饥侵,這個(gè)是用來精確匹配需要加載的類
- 使用BootstrapInstrumentBoost.inject來動(dòng)態(tài)加載應(yīng)用程序依賴類鸵赫,使應(yīng)用程序無感知,其內(nèi)部使用的是框架自己擴(kuò)展的class loader加載類 AgentClassLoader
- ServiceManager.INSTANCE.boot() 最后遍歷加載服務(wù)類躏升,并啟動(dòng)服務(wù)
我們來看一下ServiceManager.INSTANCE.boot()方法
public void boot() {
bootedServices = loadAllServices();
prepare();
startup();
onComplete();
}
其實(shí)代碼很清晰
- loadAllServices 加載所有服務(wù)
- prepare做一些服務(wù)啟動(dòng)前的準(zhǔn)備工作辩棒,其實(shí)是調(diào)用服務(wù)實(shí)現(xiàn)類的prepare方法
- startup 是調(diào)用服務(wù)類的boot方法來啟動(dòng)服務(wù)
- onComlete是在服務(wù)啟動(dòng)后給服務(wù)做一些回調(diào)
loadAllServices使用的是JDK SPI加載機(jī)制來加載服務(wù)類,資源文件在resource目錄下面膨疏,加載的服務(wù)有
我們來看一下最簡單的JVMService的實(shí)現(xiàn)
可以發(fā)現(xiàn)JVMService其實(shí)是一個(gè)實(shí)現(xiàn)了Runnable接口的實(shí)現(xiàn)類盗温,它的boot方法其實(shí)只是開啟了兩個(gè)定時(shí)調(diào)度的線程池,真正做事情的是run方法成肘,那么run方法如何調(diào)用的呢?
@Override
public void boot() throws Throwable {
collectMetricFuture = Executors.newSingleThreadScheduledExecutor(
new DefaultNamedThreadFactory("JVMService-produce"))
.scheduleAtFixedRate(new RunnableWithExceptionProtection(
this,
new RunnableWithExceptionProtection.CallbackWhenException() {
@Override
public void handle(Throwable t) {
LOGGER.error("JVMService produces metrics failure.", t);
}
}
), 0, 1, TimeUnit.SECONDS);
sendMetricFuture = Executors.newSingleThreadScheduledExecutor(
new DefaultNamedThreadFactory("JVMService-consume"))
.scheduleAtFixedRate(new RunnableWithExceptionProtection(
sender,
new RunnableWithExceptionProtection.CallbackWhenException() {
@Override
public void handle(Throwable t) {
LOGGER.error("JVMService consumes and upload failure.", t);
}
}
), 0, 1, TimeUnit.SECONDS);
}
我們發(fā)現(xiàn)線程池真正調(diào)用的是RunnableWithExceptionProtection斧蜕,傳入的第一個(gè)參數(shù)是this,所以我們猜測就是這個(gè)方法調(diào)用的run方法双霍,結(jié)果確實(shí)如此
public class RunnableWithExceptionProtection implements Runnable {
private Runnable run;
private CallbackWhenException callback;
public RunnableWithExceptionProtection(Runnable run, CallbackWhenException callback) {
this.run = run;
this.callback = callback;
}
@Override
public void run() {
try {
run.run();
} catch (Throwable t) {
callback.handle(t);
}
}
public interface CallbackWhenException {
void handle(Throwable t);
}
}
服務(wù)端代碼解讀
服務(wù)端的啟動(dòng)類是OAPServerStartUp,調(diào)用的是OAPServerBootstrap.start方法
/**
* Starter core. Load the core configuration file, and initialize the startup sequence through {@link ModuleManager}.
*/
@Slf4j
public class OAPServerBootstrap {
public static void start() {
String mode = System.getProperty("mode");
RunningMode.setMode(mode);
ApplicationConfigLoader configLoader = new ApplicationConfigLoader();
ModuleManager manager = new ModuleManager();
try {
ApplicationConfiguration applicationConfiguration = configLoader.load();
manager.init(applicationConfiguration);
manager.find(TelemetryModule.NAME)
.provider()
.getService(MetricsCreator.class)
.createGauge("uptime", "oap server start up time", MetricsTag.EMPTY_KEY, MetricsTag.EMPTY_VALUE)
// Set uptime to second
.setValue(System.currentTimeMillis() / 1000d);
log.info("Version of OAP: {}", Version.CURRENT);
if (RunningMode.isInitMode()) {
log.info("OAP starts up in init mode successfully, exit now...");
System.exit(0);
}
} catch (Throwable t) {
log.error(t.getMessage(), t);
System.exit(1);
}
}
}
上面的代碼其實(shí)做了三件事情
- 使用ApplicationConfigLoader加載服務(wù)端配置
- 將請(qǐng)求委托給ModuleManager處理
- 使用MetricsCreator上傳服務(wù)啟動(dòng)數(shù)據(jù)
那么ModuleManager又是什么批销?
OAP 使用 ModuleManager(組件管理器)管理多個(gè) Module(組件)洒闸,一個(gè) Module 可以對(duì)應(yīng)多個(gè) ModuleProvider(組件服務(wù)提供者),ModuleProvider 是 Module 底層真正的實(shí)現(xiàn)均芽。
在 OAP 服務(wù)啟動(dòng)時(shí)丘逸,一個(gè) Module 只能選擇使用一個(gè) ModuleProvider 對(duì)外提供服務(wù)。一個(gè) ModuleProvider 可能支撐了一個(gè)非常復(fù)雜的大功能掀宋,在一個(gè) ModuleProvider 中深纲,可以包含多個(gè) Service ,一個(gè) Service 實(shí)現(xiàn)了一個(gè) ModuleProvider 中的一部分功能劲妙,通過將多個(gè) Service 進(jìn)行組裝集成湃鹊,可以得到 ModuleProvider 的完整功能。
ModuleManager就是微內(nèi)核的啟動(dòng)管理類镣奋,ModuleProvider是對(duì)Service進(jìn)行劃分管理币呵,真正的服務(wù)是Service,我們來看一下ModuleManager的init方法
/**
* Init the given modules
*/
public void init(
ApplicationConfiguration applicationConfiguration) throws ModuleNotFoundException, ProviderNotFoundException, ServiceNotProvidedException, CycleDependencyException, ModuleConfigException, ModuleStartException {
String[] moduleNames = applicationConfiguration.moduleList();
ServiceLoader<ModuleDefine> moduleServiceLoader = ServiceLoader.load(ModuleDefine.class);
ServiceLoader<ModuleProvider> moduleProviderLoader = ServiceLoader.load(ModuleProvider.class);
HashSet<String> moduleSet = new HashSet<>(Arrays.asList(moduleNames));
for (ModuleDefine module : moduleServiceLoader) {
if (moduleSet.contains(module.name())) {
module.prepare(this, applicationConfiguration.getModuleConfiguration(module.name()), moduleProviderLoader);
loadedModules.put(module.name(), module);
moduleSet.remove(module.name());
}
}
// Finish prepare stage
isInPrepareStage = false;
if (moduleSet.size() > 0) {
throw new ModuleNotFoundException(moduleSet.toString() + " missing.");
}
BootstrapFlow bootstrapFlow = new BootstrapFlow(loadedModules);
bootstrapFlow.start(this);
bootstrapFlow.notifyAfterCompleted();
}
使用模塊管理的好處是便于服務(wù)管理,同一類服務(wù)可以同時(shí)啟動(dòng)侨颈,服務(wù)與服務(wù)之間可以使用線程隔離余赢,上面的方法其實(shí)只做了兩件事情
- 使用SPI加載根據(jù)配置加載模塊ModuleProvider和Service,將ModuleDefine加到集合里面
- 調(diào)用ModuleDefine的prepare方法哈垢,其實(shí)它本質(zhì)是調(diào)用ModuleProvider的prepare方法
- 將模塊委托給BootstrapFlow進(jìn)行加載啟動(dòng)
我們可以在server-core項(xiàng)目的resource目錄下找到定義的模塊
org.apache.skywalking.oap.server.core.storage.StorageModule
org.apache.skywalking.oap.server.core.cluster.ClusterModule
org.apache.skywalking.oap.server.core.CoreModule
org.apache.skywalking.oap.server.core.query.QueryModule
org.apache.skywalking.oap.server.core.alarm.AlarmModule
org.apache.skywalking.oap.server.core.exporter.ExporterModule
以及ModuleProvider,其實(shí)只有一個(gè)
org.apache.skywalking.oap.server.core.CoreModuleProvider
其實(shí)是CoreModuleProvider是OAP的真正啟動(dòng)類妻柒,這個(gè)類異常復(fù)雜加載了服務(wù)端所有需要注冊和啟動(dòng)的服務(wù),這個(gè)模塊只是核心加載模塊温赔,其它模塊通過loadedProvider屬性持有這個(gè)核心加載模塊的引用
最后我們來看一下BootstrapFlow的start方法
@SuppressWarnings("unchecked")
void start(
ModuleManager moduleManager) throws ModuleNotFoundException, ServiceNotProvidedException, ModuleStartException {
for (ModuleProvider provider : startupSequence) {
log.info("start the provider {} in {} module.", provider.name(), provider.getModuleName());
provider.requiredCheck(provider.getModule().services());
provider.start();
}
}
其實(shí)調(diào)用的是就是ModuleProvider的start方法蛤奢,最后我們看一下coreModuleProvider全貌
其中prepare和start方法我們剛才都有提到鬼癣,其中start方法啟動(dòng)的是RemoteClientManager這個(gè)service類,其它方面限于篇幅啤贩,有興趣的讀者可以自行研究待秃。