最全面!一文讓你看懂無侵入的微服務(wù)探針原理?。?/h1>
時(shí)間:2023-06-27 23:54:01 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2023-06-27 23:54:01 來源:網(wǎng)站運(yùn)營
最全面!一文讓你看懂無侵入的微服務(wù)探針原理?。。?h2 data-first-child>前言 隨著微服務(wù)架構(gòu)的興起,應(yīng)用行為的復(fù)雜性顯著提高,為了提高服務(wù)的可觀察性,分布式監(jiān)控系統(tǒng)變得十分重要。
基于 Google 的 Dapper 論文,發(fā)展出了很多有名的監(jiān)控系統(tǒng):Zipkin、Jaeger、Skywalking 以及想一統(tǒng)江湖的 OpenTelemetry 等。一眾廠家和開源愛好者圍繞著監(jiān)控?cái)?shù)據(jù)的采集、收集、存儲(chǔ)以及展示做出了不少出色的設(shè)計(jì)。
時(shí)至今日即使是個(gè)人開發(fā)者也能依賴開源產(chǎn)品,輕松的搭建一套完備的監(jiān)控系統(tǒng)。但作為監(jiān)控服務(wù)的提供者,必須要做好與業(yè)務(wù)的解綁,來降低用戶接入、版本更新、問題修復(fù)、業(yè)務(wù)止損的成本。所以一個(gè)可插拔、無侵入的采集器成為一眾廠家必備的殺手锏。
為了獲取服務(wù)之間調(diào)用鏈信息,采集器通常需要在方法的前后做埋點(diǎn)。在 Java 生態(tài)中,常見的埋點(diǎn)方式有兩種:依賴 SDK 手動(dòng)埋點(diǎn);利用 Javaagent 技術(shù)來做無侵入埋點(diǎn)。下面圍繞著 無侵入埋點(diǎn)的技術(shù)與原理為大家做一個(gè)全面的介紹。
無侵入的采集器(探針)
分布式監(jiān)控系統(tǒng)中,模塊可以分為:采集器(Instrument)、發(fā)送器(TransPort)、收集器(Collector)、存儲(chǔ)(Srotage)、展示(API&UI)。
zipkin 的架構(gòu)圖示例
采集器將收集的監(jiān)控信息,從應(yīng)用端發(fā)送給收集器,收集器進(jìn)行存儲(chǔ),最終提供給前端查詢。
采集器收集的信息,我們稱之為 Trace (調(diào)用鏈)。一條 Trace 擁有唯一的標(biāo)識(shí) traceId,由自上而下的樹狀 span 組成。每個(gè) span 除了spanId 外,還擁有 traceId 、父 spanId,這樣就可以還原出一條完整的調(diào)用鏈關(guān)系。
為了生成一條 span , 我們需要在方法調(diào)用的前后放入埋點(diǎn)。比如一次 http 調(diào)用,我們?cè)?execute() 方法的前后加入埋點(diǎn),就可以得到完整的調(diào)用方法信息,生成一個(gè) span 單元。
在 Java 生態(tài)中,常見的埋點(diǎn)方式有兩種:依賴 SDK 手動(dòng)埋點(diǎn);利用 Javaagent 技術(shù)來做無侵入埋點(diǎn)。不少開發(fā)者接觸分布式監(jiān)控系統(tǒng),是從 Zipkin 開始的,最經(jīng)典的是搞懂 X-B3 trace協(xié)議,使用 Brave SDK,手動(dòng)埋點(diǎn)生成 trace。但是 SDK 埋點(diǎn)的方式,無疑和業(yè)務(wù)邏輯做了深深的依賴,當(dāng)升級(jí)埋點(diǎn)時(shí),必須要做代碼的變更。
那么如何和業(yè)務(wù)邏輯解綁呢?
Java 還提供了另外一種方式:依賴 Javaagent 技術(shù),修改目標(biāo)方法的字節(jié)碼,做到無侵入的埋點(diǎn)。這種利用 Javaagent 的方式的采集器,也叫做探針。在應(yīng)用程序啟動(dòng)時(shí)使用 -javaagent ,或者運(yùn)行時(shí)使用 attach( pid) 方式,就可以將探針包導(dǎo)入應(yīng)用程序,完成埋點(diǎn)的植入。無侵入的方式,可以做到無感的熱升級(jí)。用戶不需要理解深層的原理,就可以使用完整的監(jiān)控服務(wù)。目前眾多開源監(jiān)控產(chǎn)品已經(jīng)提供了豐富的 java 探針庫,作為監(jiān)控服務(wù)的提供者,進(jìn)一步降低了開發(fā)成本。
想要開發(fā)一個(gè)無侵入的探針,可以分為三個(gè)部分:Javaagent ,字節(jié)碼增強(qiáng)工具,trace 生成邏輯。下面會(huì)為大家介紹這些內(nèi)容。
基礎(chǔ)概念
使用 JavaAgent 之前 讓我們先了解一下 Java 相關(guān)的知識(shí)。
什么是字節(jié)碼?
類 c 語言 Java 從1994年被 sun 公司發(fā)明以來,依賴著 "一次編譯、到處運(yùn)行" 特性,迅速的風(fēng)靡全球。與 C++ 不同的是,Java 將所有的源碼首先編譯成 class (字節(jié)碼)文件,再依賴各種不同平臺(tái)上的 JVM(虛擬機(jī))來解釋執(zhí)行字節(jié)碼,從而與硬件解綁。class 文件的結(jié)構(gòu)是一個(gè) table 表,由眾多 struct 對(duì)象拼接而成。
字節(jié)碼的字段屬性
讓我們編譯一個(gè)簡單的類`Demo.java`
package com.httpserver;public class Demo { private int num = 1; public int add() { num = num + 2; return num; }}
用16進(jìn)制打開 Demo.class 文件,解析后字段也是有很多 struct 字段組成:比如常量池、父類信息、方法信息等。
JDK 自帶的解析工具 javap ,可以以人類可讀的方式打印 class 文件,其結(jié)果也和上述一致
什么是JVM?
JVM(Java Virtual Machine),一種能夠運(yùn)行 Java bytecode 的虛擬機(jī),是Java 體系的一部分。JVM 有自己完善的硬體架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JVM 屏蔽了與具體操作系統(tǒng)平臺(tái)相關(guān)的信息,使得Java 程序只需生成在 JVM 上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行, 這便是 "一次編譯,到處運(yùn)行" 的真正含義 。
作為一種編程語言的虛擬機(jī),實(shí)際上不只是專用于 Java 語言,只要生成的編譯文件符合 JVM 對(duì)加載編譯文件格式要求,任何語言都可以由JVM編譯運(yùn)行。
同時(shí) JVM 技術(shù)規(guī)范未定義使用的垃圾回收算法及優(yōu)化 Java 虛擬機(jī)指令的內(nèi)部算法等,僅僅是描述了應(yīng)該具備的功能,這主要是為了不給實(shí)現(xiàn)者帶來過多困擾與限制。正是由于恰到好處的描述,這給各廠商留下了施展的空間。
維基百科:已有的 JVM 比較
其中
HotSpot(Orcale) 與性能更好的 OpenJ9(IBM) 被廣大開發(fā)者喜愛。
JVM 的內(nèi)存模型
JVM 部署之后,每一個(gè) Java 應(yīng)用的啟動(dòng),都會(huì)調(diào)用 JVM 的 lib 庫去申請(qǐng)資源創(chuàng)建一個(gè) JVM 實(shí)例。JVM 將內(nèi)存分做了不同區(qū)域,如下是 JVM 運(yùn)行時(shí)的內(nèi)存模型:
- 方法區(qū):用于存放的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
- 堆:所有線程共享,放置 object 對(duì)象與數(shù)組,也是 GC (垃圾收集器的主要區(qū)域)
- 虛機(jī)棧&程序計(jì)數(shù)器:線程私有的,每一個(gè)新的線程都會(huì)分配對(duì)應(yīng)的內(nèi)存對(duì)象。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
雙親委派加載機(jī)制
Java 應(yīng)用程序在啟動(dòng)和運(yùn)行時(shí),一個(gè)重要的動(dòng)作是:加載類的定義,并創(chuàng)建實(shí)例。這依賴于 JVM 自身的 ClassLoader 機(jī)制。
雙親委派
一個(gè)類必須由一個(gè) ClassLoader 負(fù)責(zé)加載,對(duì)應(yīng)的 ClassLoader 還有父 ClassLoader ,尋找一個(gè)類的定義會(huì)自下而上的查找,這就是雙親委派模型。
為了節(jié)省內(nèi)存,JVM 并不是將所有的類定義都放入內(nèi)存,而是
- 啟動(dòng)時(shí):將必要的類通過 ClassLoader 加載到內(nèi)存
- 運(yùn)行時(shí):創(chuàng)建一個(gè)新實(shí)例時(shí),優(yōu)先從內(nèi)存中尋找,否則加載進(jìn)內(nèi)存
- 執(zhí)行方法:尋找方法的定義,將局部變量和方法的字節(jié)碼放入虛機(jī)棧中,最終返回計(jì)算結(jié)果。當(dāng)然靜態(tài)方法會(huì)有所區(qū)別。
這樣的設(shè)計(jì)讓我們聯(lián)想到:如果能在加載時(shí)或者直接替換已經(jīng)加載的類定義,就可以完成神奇的增強(qiáng)。
JVM tool Interface
默默無聞的 JVM 屏蔽了底層的復(fù)雜,讓開發(fā)者專注于業(yè)務(wù)邏輯。除了啟動(dòng)時(shí)通過 java -jar 帶內(nèi)存參數(shù)之外,其實(shí)有一套專門接口提供給開發(fā)者,那就是 JVM tool Interface 。
JVM TI 是一個(gè)雙向接口。JVM TI Client 也叫 agent ,基于 event 事件機(jī)制。它接受事件,并執(zhí)行對(duì) JVM 的控制,也能對(duì)事件進(jìn)行回應(yīng)。
它有一個(gè)重要的特性 - Callback (回調(diào)函數(shù) )機(jī)制:JVM 可以產(chǎn)生各種事件,面對(duì)各種事件,它提供了一個(gè) Callback 數(shù)組。每個(gè)事件執(zhí)行時(shí),都會(huì)調(diào)用 Callback 函數(shù), 所以編寫 JVM TI Client 的核心就是放置 Callback 函數(shù)。
正是有了這個(gè)機(jī)制能讓我們向 JVM 發(fā)送指令,加載新的類定義。
JavaAgent
現(xiàn)在我們?cè)囍伎枷拢喝绾稳ツЦ膽?yīng)用程序中的方法的定義呢?
這有點(diǎn)像大象放入冰箱需要幾步:
- 按照字節(jié)碼的規(guī)范生成新的類
- 使用 JVM TI ,命令 JVM 將類加載到對(duì)應(yīng)的內(nèi)存去。
替換后,系統(tǒng)將使用我們?cè)鰪?qiáng)過的方法。
這并不容易,但幸運(yùn)的是,jdk已經(jīng)為我們準(zhǔn)備好了這樣的上層接口 instructment 包。它使用起來也是十分容易,我們下面通過一個(gè) agent 簡單示例,來講解 instructment 包的關(guān)鍵設(shè)計(jì)。
Javaagent 簡單示例
javaagent 有兩種使用 方式:
- 啟動(dòng)時(shí)加入?yún)?shù)配置 agent 包路徑 : -javaagent:/${path}/agent.jar;
- 運(yùn)行時(shí)attach 到JVM 實(shí)例的pid ,將 jar 包附著上去 :VirtualMachine.attach(pid);VirtualMachine.loadAgent("/<path>/agent.jar");
使用第一種方式的 demo
public class PreMainTraceAgent { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new DefineTransformer(), true); } static class DefineTransformer implements ClassFileTransformer{ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("premain load Class:" + className); return classfileBuffer; }
Manifest-Version: 1.0Can-Redefine-Classes: trueCan-Retransform-Classes: truePremain-Class: PreMainTraceAgent |
然后在 resources 目錄下新建目錄:META-INF,在該目錄下新建文件:MANIFREST.MF:
最后打包成 agent.jar 包
- premain() :-javaagent 方式進(jìn)入的入口。顧名思義他是在 main 函數(shù)前執(zhí)行的,制作 jar 包時(shí)需要在 MF 文件中指名入口 Premain-Class: PreMainTraceAgent
- Instrumentation:JVM 實(shí)例的句柄。無論是 -javaagent 還是 attach 上去,最終都會(huì)獲得一個(gè)實(shí)例相關(guān)的 Instrumentation。inst 中比較重要的兩個(gè)函數(shù)是 redefineClasses(ClassDefinition... definitions) 與 retransformClasses(Class<?>... classes) 通過這兩個(gè)函數(shù),我們都可以將增強(qiáng)后字節(jié)碼加入到 JVM 中
redefineClasses() 和 retransformClasses() 的區(qū)別 ?redefineClasses() 適合將新加入的類做修改,而 retransformClasses() 可以將哪些已經(jīng)加載到內(nèi)存中的類定義做替換
- ClassFileTransformer:這個(gè)接口里面有一個(gè)重要的方法 transform() ,使用者需要實(shí)現(xiàn)這個(gè)類。當(dāng)這個(gè)類被加入 inst 的內(nèi)的 Transformer 數(shù)組時(shí),每一個(gè)類的加載或修改,都會(huì)調(diào)用到該方法。類的定義相關(guān)信息,比如類二進(jìn)制定義 classfileBuffer
- addTransformer() :可以將實(shí)現(xiàn)了 ClassFileTransformer 的類加入 Instrumentation 中內(nèi)置的數(shù)組。就像一個(gè)加工廠,上一個(gè) ClassFileTransformer 處理過的類,會(huì)作為下一個(gè) ClassFileTransformer 的參數(shù)。
到了這里就會(huì)發(fā)現(xiàn),增強(qiáng)字節(jié)碼也是如此的簡單。
字節(jié)碼生成工具
通過前面的了解,有種修改字節(jié)碼也不過如此的感覺 ^_^ ?。。〉俏覀儾坏貌恢匾暳硪粋€(gè)問題,字節(jié)的如何生成的?
- 大佬:我熟悉 JVM 規(guī)范,明白每一個(gè)字節(jié)碼的含義,我可以手動(dòng)改class文件,為此我寫了一個(gè)庫 。
- 高手:我知道客戶的框架,我修改源碼,重新編譯,將二進(jìn)制替換進(jìn)去。
- 小白:字節(jié)碼我是看不懂啦,大佬寫的庫我會(huì)用就行了。
下面會(huì)介紹幾個(gè)常見的字節(jié)碼生成工具
ASM
ASM 是一個(gè)純粹的字節(jié)碼生成和分析框架。它有完整的語法分析,語義分析,可以被用來動(dòng)態(tài)生成 class 字節(jié)碼。但是這個(gè)工具還是過于專業(yè),使用者必須十分了解 JVM 規(guī)范,必須清楚替換一個(gè)函數(shù)究竟要在 class 文件做哪些改動(dòng)。ASM 提供了兩套API:
- CoreAPI 基于事件的形式表現(xiàn)類;
- TreeAPI 基于對(duì)象的方式來表現(xiàn)類
初步掌握字節(jié)碼 與JVM 內(nèi)存模型的知識(shí),可以照著官方文檔進(jìn)行簡單地類生成。
ASM 十分強(qiáng)大,被應(yīng)用于 1. OpenJDK的 lambda語法 2. Groovy 和 Koltin 的編譯器 3. 測(cè)試覆蓋率統(tǒng)計(jì)工具 Cobertura 和 Jacoco 4. 單測(cè) mock 工具,比如 Mockito 和 EasyMock 5. CGLIB ,ByteBuddy 這些動(dòng)態(tài)類生成工具。
BYTEBUDDY
ByteBuddy 是一款出眾的運(yùn)行時(shí)字節(jié)碼生成工具,基于 ASM 實(shí)現(xiàn),提供更易用的 API。被眾多分布式監(jiān)控項(xiàng)目比如 Skywalking、Datadog 等使用 作為 Java 應(yīng)用程序的探針來采集監(jiān)控信息。
以下是與其他工具的性能比較。
- Java Proxy:JDK 自帶的代理機(jī)制,可以做到托管用戶的類,以便于擴(kuò)展。但是必須給定一個(gè)接口,作用有限
- Cglib:很有名氣,但是開發(fā)的太早了,并沒有隨著 JDK 的特性一起更新。雖然它的庫依舊很有用,但是也慢慢被被使用者從項(xiàng)目中移除
- Javassit: 這個(gè)庫企圖模仿 javac 編譯器,做到運(yùn)行時(shí)轉(zhuǎn)化源代碼。這非常有雄心,然而這個(gè)難度很有挑戰(zhàn),目前為止和 javac 還有相當(dāng)大的差距。
在我們實(shí)際的使用中,ByteBuddy 的 API 確實(shí)比較友好,基本滿足了所有字節(jié)碼增強(qiáng)需求:接口、類、方法、靜態(tài)方法、構(gòu)造器方法、注解等的修改。除此之外內(nèi)置的 Matcher 接口,支持模糊匹配,可以根據(jù)名稱匹配修改符合條件的類型。
但也有缺點(diǎn),官方文檔比較舊,中文文檔少。很多重要的特性,比如切面,并未詳細(xì)介紹,往往需要看代碼注釋,和測(cè)試用例才弄懂真正的含義。如果對(duì) ByteBuddy 這個(gè)工具有興趣的同學(xué),可以關(guān)注我們的公眾號(hào),后面的文章會(huì)就 ByteBuddy 做專門的分享。
Trace 數(shù)據(jù)的生成
通過字節(jié)碼增強(qiáng),我們可以做到無侵入的埋點(diǎn),那么和 trace 的生成邏輯的關(guān)聯(lián)才算是注入靈魂。下面我們通過一個(gè)簡單例子,來展示這樣的結(jié)合是如何做到的。
Tracer API這是一個(gè)簡單的 API,用來生成 trace 消息。
public class Tracer { public static Tracer newTracer() { return new Tracer(); } public Span newSpan() { return new Span(); } public static class Span { public void start() { System.out.println("start a span"); } public void end() { System.out.println("span finish"); // todo: save span in db } }}
僅有一個(gè)方法 sayHello(String name)
目標(biāo)類 Greetingpublic class Greeting { public static void sayHello(String name) { System.out.println("Hi! " + name); }}
手動(dòng)生成 trace 消息,我們需要在方法的前后加入埋點(diǎn)
手動(dòng)埋點(diǎn)... public static void main(String[] args) { Tracer tracer = Tracer.newTracer(); // 生成新的span Tracer.Span span = tracer.newSpan(); // span 的開始與結(jié)束 span.start(); Greeting.sayHello("developer"); span.end();}...
無侵入埋點(diǎn)字節(jié)增強(qiáng)可以讓我們無需修改源代碼?,F(xiàn)在我們可以定義一個(gè)簡單的切面,將 span 生成邏輯放入切面中,然后利用 Bytebuddy 將埋點(diǎn)植入。
TraceAdvice 將 trace 生成邏輯放入切面中去
public class TraceAdvice { public static Tracer.Span span = null; public static void getCurrentSpan() { if (span == null) { span = Tracer.newTracer().newSpan(); } } /** * @param target 目標(biāo)類實(shí)例 * @param clazz 目標(biāo)類class * @param method 目標(biāo)方法 * @param args 目標(biāo)方法參數(shù) */ @Advice.OnMethodEnter public static void onMethodEnter(@Advice.This(optional = true) Object target, @Advice.Origin Class<?> clazz, @Advice.Origin Method method, @Advice.AllArguments Object[] args) { getCurrentSpan(); span.start(); } /** * @param target 目標(biāo)類實(shí)例 * @param clazz 目標(biāo)類class * @param method 目標(biāo)方法 * @param args 目標(biāo)方法參數(shù) * @param result 返回結(jié)果 */ @Advice.OnMethodExit(onThrowable = Throwable.class) public static void onMethodExit(@Advice.This(optional = true) Object target, @Advice.Origin Class<?> clazz, @Advice.Origin Method method, @Advice.AllArguments Object[] args, @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result) { span.end(); span = null; }}
- onMethodEnter:方法進(jìn)入時(shí)調(diào)用。Bytebuddy 提供了一系列注解,帶有 @Advice.OnMethodExit 的靜態(tài)方法,可以被植入方法開始的節(jié)點(diǎn)。我們可以獲取方法的詳細(xì)信息,甚至修改傳入?yún)?shù),跳過目標(biāo)方法的執(zhí)行。
- OnMethodExit:方法結(jié)束時(shí)調(diào)用。類似 onMethodEnter,但是可以捕獲方法體拋出的異常,修改返回值。
植入 Advice將Javaagent 獲取的 Instrumentation 句柄 ,傳入給 AgentBuilder (Bytebuddy 的 API)
public class PreMainTraceAgent { public static void premain(String agentArgs, Instrumentation inst) { // Bytebuddy 的 API 用來修改 AgentBuilder agentBuilder = new AgentBuilder.Default() .with(AgentBuilder.PoolStrategy.Default.EXTENDED) .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(new WeaveListener()) .disableClassFormatChanges(); agentBuilder = agentBuilder // 匹配目標(biāo)類的全類名 .type(ElementMatchers.named("baidu.bms.debug.Greeting")) .transform(new AgentBuilder.Transformer() { @Override public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { return builder.visit( // 織入切面 Advice.to(TraceAdvice.class) // 匹配目標(biāo)類的方法 .on(ElementMatchers.named("sayHello")) ); } }); agentBuilder.installOn(inst); } // 本地啟動(dòng) public static void main(String[] args) throws Exception { ByteBuddyAgent.install(); Instrumentation inst = ByteBuddyAgent.getInstrumentation(); // 增強(qiáng) premain(null, inst); // 調(diào)用 Class greetingType = Greeting.class. getClassLoader().loadClass(Greeting.class.getName()); Method sayHello = greetingType.getDeclaredMethod("sayHello", String.class); sayHello.invoke(null, "developer"); }
本地調(diào)試除了制作 agent.jar 之外,我們本地調(diào)試時(shí)可以在 main 函數(shù)中啟動(dòng),如上面提示的那樣。
打印結(jié)果
WeaveListener onTransformation : baidu.bms.debug.Greetingstart a spanHi! developerspan finishDisconnected from the target VM, address: '127.0.0.1:61646', transport: 'socket'可以看到,我們已經(jīng)在目標(biāo)方法的前后,已經(jīng)加入 trace 的生成邏輯。
實(shí)際的業(yè)務(wù)中,我們往往只需要對(duì)應(yīng)用程序使用的框做捕獲,比如對(duì) Spring 的 RestTemplate 方法,就可以獲得準(zhǔn)確的 Http 方法的調(diào)用信息。這種依賴這種字節(jié)碼增強(qiáng)的方式,最大程度的做到了和業(yè)務(wù)解耦。
What`s more ?
在實(shí)際的業(yè)務(wù)中,我們也積累不少踩坑經(jīng)驗(yàn) :
1. 有沒有一個(gè)好的探針框架,讓我 "哼哧哼哧" 寫業(yè)務(wù)就行 ?
2. 如何做到無感的熱升級(jí),讓用戶在產(chǎn)品上輕松設(shè)置埋點(diǎn) ?
3. ByteBuddy 到底該怎么用,切面的注解都是什么意思?
4. Javaagent + Istio 如何讓 Dubbo 微服務(wù)治理框無感的遷移到 ServiceMesh 上 ?
感興趣的同學(xué),可以關(guān)注我們的公眾號(hào),后面我們會(huì)為大家?guī)砀实姆窒恚?br>
作者簡介:孫啟元,百度研發(fā)工程師,現(xiàn)就職于百度基礎(chǔ)架構(gòu)部云原生團(tuán)隊(duì),對(duì)云原生微服務(wù)監(jiān)控、Kubernetes 等方向有深入的研究和實(shí)踐經(jīng)驗(yàn)。
上文提到的微服務(wù)監(jiān)控技術(shù)已經(jīng)在百度智能云 CNAP 產(chǎn)品中落地。
了解更多微服務(wù)、云原生技術(shù)的相關(guān)信息,請(qǐng)關(guān)注我們的微信公眾號(hào)【云原生計(jì)算】!