使用Elastic APM监控你的.NET Core应用
前言
在應(yīng)用實(shí)際的運(yùn)維過(guò)程中,我們需要更多的日志和監(jiān)控來(lái)讓我們對(duì)自己的應(yīng)用程序的運(yùn)行狀況有一個(gè)全方位的了解。然而對(duì)于大部分開發(fā)者而言,平時(shí)大家所關(guān)注的更多的是如何更優(yōu)雅的實(shí)現(xiàn)業(yè)務(wù),或者是如何讓應(yīng)用的響應(yīng)速度更快等等與編碼相關(guān)的技術(shù),對(duì)于應(yīng)用程序的監(jiān)控,可能還停留在日志文件的層面,而且大多數(shù)是出了事故被人為發(fā)現(xiàn)后,才通過(guò)日志嘗試去定位問(wèn)題。
本文所準(zhǔn)備介紹的Elastic APM是一套用于監(jiān)控應(yīng)用各項(xiàng)指標(biāo),比如系統(tǒng)響應(yīng)時(shí)間、異常、EF執(zhí)行的SQL記錄等等,并且可以將這些記錄組織成一個(gè)可追溯的鏈路,方便查詢問(wèn)題。此外,Elastic APM還可以通過(guò)Kibana來(lái)做非常漂亮的可視化展示,方便我們定位和發(fā)現(xiàn)問(wèn)題。
廢話不再多說(shuō),我們開始實(shí)戰(zhàn)~Elastic APM介紹
Elastic APM的由下面四個(gè)組件所組成,如下圖:
APM Agent
APM Agent是安裝到你的.NET Core程序中的一個(gè)Nuget包,他用于性能、錯(cuò)誤等各類數(shù)據(jù)的收集,并將收集到的數(shù)據(jù)緩存起來(lái)分批發(fā)送到APM Server。當(dāng)然,除了.NET Core使用的Nuget包,他還可以支持很多其他的語(yǔ)言,比如Java,Node.Js,Python等
支持的語(yǔ)言列表請(qǐng)參考這里:https://www.elastic.co/guide/en/apm/agent/index.html
APM Server
APM Server是部署在服務(wù)器端的一個(gè)用于接收Agent發(fā)來(lái)的數(shù)據(jù)包的應(yīng)用程序,并根據(jù)這些數(shù)據(jù)包自動(dòng)創(chuàng)建文檔,將數(shù)據(jù)轉(zhuǎn)存到Elastic Server中。
Elastic Search
這個(gè)相信大家都很熟悉了,他就是一個(gè)基于Lucene實(shí)現(xiàn)的高性能、分布式的全文搜索引擎,用于快速、實(shí)時(shí)的存儲(chǔ)、搜索和分析大量數(shù)據(jù)。在這里來(lái)說(shuō),他提供的是數(shù)據(jù)存儲(chǔ)和搜索能力!
Kibana
如果你熟悉Elastic Search,那么你一定多少會(huì)了解Kibana,Kibana是開源的分析和可視化平臺(tái),他能與Elastic Search進(jìn)行很好的協(xié)同,幫助你快速的可視化存儲(chǔ)在Elastic Search中的數(shù)據(jù),并做成各種各樣漂亮的報(bào)表、圖形等。
環(huán)境準(zhǔn)備
在本次的實(shí)戰(zhàn)過(guò)程中,我們需要以下的東西:
Elastic Search
Kibana
APM Server
一個(gè)基于.NET Standard 2.0 + 的項(xiàng)目
Elastic Search的安裝:https://www.cnblogs.com/baiyunchen/p/11227144.html
Kibana的安裝:
我的環(huán)境是Centos 7,所以照著https://www.elastic.co/guide/en/kibana/7.3/rpm.html 這個(gè)官網(wǎng)教程安裝的,整個(gè)過(guò)程很簡(jiǎn)單:
下載Kibana RPM包(采用這種方式是因?yàn)橛脃um install網(wǎng)速太慢,所以我用迅雷下載完成rpm文件后上傳到Linux機(jī)器中)
執(zhí)行命令rpm --install? “下載的文件名” 進(jìn)行安裝
安裝完成后,到/etc/kibana/kibana.yml文件中在文件末尾增加以下配置:
1 2 3 4 5 | server.host: 0.0.0.0 server.name: 主機(jī)IP server.port: 一個(gè)你喜歡的端口號(hào) elasticsearch.hosts: ["已安裝好的ES地址,多個(gè)之間用逗號(hào)隔開"] logging.dest: /var/log/kibana.log |
將Kibana安裝為系統(tǒng)服務(wù)并啟動(dòng)
1 2 3 4 | sudo /bin/systemctl daemon-reload sudo /bin/systemctl enable kibana.service sudo systemctl start kibana.service |
這里大家一定要注意Elastic Search的版本和Kibana一定要匹配,不然會(huì)報(bào)錯(cuò)的。(我的ES是前段時(shí)間裝的,所以會(huì)有這問(wèn)題,如果大家一口氣安裝所有的,應(yīng)該沒(méi)啥問(wèn)題)
如果不幸遇到了問(wèn)題,可以通過(guò)配置文件中l(wèi)ogging.dest中配置的路徑查看日志。
APM Server的安裝
APM Server的安裝跟Kibana的安裝類似,過(guò)程如下:
下載RPM包,包在這個(gè)頁(yè)面找你需要的版本,也需要跟ES、Kibana的版本一致,不然你懂得~?https://www.elastic.co/cn/downloads/past-releases#apm-server
執(zhí)行rpm --install “下載的文件名”進(jìn)行安裝
在文件夾/etc/amp-server中修改配置文件apm-server.yml,將配置文件最開始的host: “l(fā)ocalhost:8200”修改成“0.0.0.0:8200”,以便讓他能允許通過(guò)ip:端口號(hào)的方式訪問(wèn), 并在配置的最后面添加如下配置:
1 2 | output.elasticsearch: ????hosts: ["已安裝好的ES地址,多個(gè)之間用逗號(hào)隔開"] |
將apm-server安裝為系統(tǒng)服務(wù)并啟動(dòng)
1 2 3 4 | sudo /bin/systemctl daemon-reload sudo /bin/systemctl enable apm-server.service sudo systemctl start apm-server.service |
執(zhí)行上述操作完成后,在瀏覽器中嘗試打開服務(wù)器Ip:8200,最終如果APM Server安裝的沒(méi)有問(wèn)題,則瀏覽器中會(huì)打印出類似于如下的內(nèi)容:
1 2 3 4 5 | { ??"build_date": "2019-06-20T14:39:23Z", ??"build_sha": "9a099b63c53eac8c55707df96193143ec66337e9", ??"version": "7.2.0" } |
此時(shí)我們?cè)跒g覽器中打開Kibana,然后點(diǎn)擊Add APM
然后將新打開的頁(yè)面往下滾動(dòng),點(diǎn)擊Check APM Server Status按鈕,如果出現(xiàn)You have correctly setup APM Server則說(shuō)明安裝完成~
到這里為止,我們的安裝工作就全部完成了,接下來(lái),我們嘗試將.NET Core與Elastic APM集成起來(lái),一起繼續(xù)吧~
.NET Core 應(yīng)用集成
我們創(chuàng)建一個(gè)Demo項(xiàng)目,來(lái)用于測(cè)試APM的各項(xiàng)功能。
項(xiàng)目的地址請(qǐng)參考GitHub:
引用依賴包
我們需要從Nuget引用相關(guān)的SDK來(lái)與我們的應(yīng)用做集成,其實(shí)就是引用我們最開始說(shuō)的APM Agent的部分,在Nuget中,我們引用Elastic.Apm.NetCoreAll這個(gè)包。
依賴這個(gè)包其實(shí)相當(dāng)于自動(dòng)依賴了如下三個(gè)包,你也可以根據(jù)需要只依賴其中的一部分。
Elastic.Apm
Elastic.Apm.AspNetCore
Elastic.Apm.EntityFrameworkCore
這里我們?yōu)榱撕?jiǎn)單起見(jiàn),直接印用Elastic.Apm.NetCoreAll這個(gè)包
將Agent添加到.NET Core
找到.NET Core的StartUp文件,在里面的Configure方法中添加如下代碼:
1 2 3 4 | public?void?Configure(IApplicationBuilder app, IHostingEnvironment env) { ????app.UseAllElasticApm(Configuration); } |
然后在application.json中添加如下內(nèi)容:
1 2 3 4 5 6 | { ??"ElasticApm": { ????"LogLevel": "Error", ????"ServiceName"?: "MyApp", ??} } |
此時(shí)我們將項(xiàng)目啟動(dòng)起來(lái),隨便的刷新幾下,然后回到Kibana中,在剛才的頁(yè)面中往下滾動(dòng),選擇.NET,然后點(diǎn)擊Check Agent Status按鈕,如果順利,就會(huì)顯示“Data successfully received from one or more agents”,如果不幸沒(méi)能顯示這句話,可以通過(guò)VS的Diagnostic Tools中的Event跟蹤一下,看看是不是哪里沒(méi)有配置對(duì)
?
監(jiān)控?cái)?shù)據(jù)查看
在Kibana的Add APM頁(yè)面的最下方,找到Load Kibana Objects,來(lái)創(chuàng)建索引,然后點(diǎn)擊APM dashboard按鈕,就可以進(jìn)入APM數(shù)據(jù)的查看頁(yè)面。
?
點(diǎn)擊APM Dashboard按鈕后,展示的頁(yè)面如下:
該頁(yè)面中的功能分為Services、Traces兩個(gè)大的功能模塊,先來(lái)簡(jiǎn)單了解一下這兩個(gè)Tab頁(yè)中對(duì)應(yīng)的功能。
Services
下面的列表中顯示的XianDotnetCommunity其實(shí)就是你在配置文件中配置的ServiceName,點(diǎn)擊這個(gè)名字進(jìn)入,又可以看到如下的報(bào)表,里面有Transactions,Errors,Metrics三個(gè)Tab頁(yè)。
?
?
其中
Transactions:展示的當(dāng)前應(yīng)用請(qǐng)求情況的概覽,包括請(qǐng)求響應(yīng)時(shí)長(zhǎng)、請(qǐng)求調(diào)用次數(shù)等等
Errors:程序中的異常列表
Metrics:應(yīng)用程序所在機(jī)器的CPU/內(nèi)存使用情況
PS:其實(shí)我覺(jué)得非常需要一個(gè)當(dāng)前應(yīng)用程序所消耗的內(nèi)存和CPU的值,但是貌似.NET Core版本的代理沒(méi)有實(shí)現(xiàn)這些功能,期待未來(lái)的更新吧
Traces
里面是用于做鏈路追蹤的視圖,首頁(yè)包含所有事務(wù)的名稱列表以及響應(yīng)時(shí)間等
點(diǎn)擊具體的事務(wù)進(jìn)去,可以看到這個(gè)事務(wù)經(jīng)過(guò)的鏈路列表以及更詳細(xì)的一些響應(yīng)信息,從而幫你分析出整個(gè)鏈路中的瓶頸,更多內(nèi)容我們?cè)谙旅婕?xì)講。
探索更多
Elastic APM還有很多其他的功能,比如鏈路追蹤、數(shù)據(jù)庫(kù)調(diào)用執(zhí)行,讓我們來(lái)一起探索吧~API調(diào)用鏈路追蹤
如果你了解過(guò)微服務(wù)架構(gòu),那你一定了解鏈路追蹤這個(gè)概念。那什么是鏈路追蹤呢?舉個(gè)栗子:
有個(gè)服務(wù)A,他會(huì)依賴服務(wù)B,C,而服務(wù)B又會(huì)依賴服務(wù)D,E,服務(wù)C又依賴F,G(省略無(wú)數(shù)依賴關(guān)系),然后有一天,服務(wù)A變得非常慢,那到底該怎么定位是哪個(gè)服務(wù)慢呢?此時(shí)鏈路最終就派上用場(chǎng)了~
我們來(lái)簡(jiǎn)單模擬一下這種嵌套的調(diào)用:
在一個(gè)WebAPI項(xiàng)目Demo1中有一個(gè)ConsumerController,他里面有一個(gè)API A,里面調(diào)用了另外一個(gè)WEB API項(xiàng)目Demo2中的接口B/C/D/E。代碼大致如下:
項(xiàng)目甲:
[Route("api/consumer")] [ApiController] public?class?ConsumerController : ControllerBase { ????private?readonly?IHttpClientFactory _httpClientFactory; ????public?ConsumerController(IHttpClientFactory httpClientFactory) ????{ ????????_httpClientFactory = httpClientFactory; ????} ????private?const?string?baseUri = "http://localhost:54597"; ????[HttpGet("a")] ????public?async Task<string> A() ????{ ????????var?client = _httpClientFactory.CreateClient(); ????????Thread.Sleep(new?Random().Next(1, 1500)); ????????var?b = await client.GetStringAsync($"{baseUri}/api/data-source/b"); ????????var?c = await client.GetStringAsync($"{baseUri}/api/data-source/c"); ????????var?d = await client.GetStringAsync($"{baseUri}/api/data-source/d"); ????????var?e = await client.GetStringAsync($"{baseUri}/api/data-source/e"); ????????return?$"b= & c={c} & d=ze8trgl8bvbq & e={e}"; ????} } |
項(xiàng)目乙:
[Route("api/data-source")] [ApiController] public?class?DataSourceController : ControllerBase { ????[HttpGet("b")] ????public?async Task<string> B() ????{ ????????Thread.Sleep(new?Random().Next(1, 1500)); ????????return?"B"; ????} ????[HttpGet("c")] ????public?async Task<string> C() ????{ ????????Thread.Sleep(new?Random().Next(1, 1500)); ????????return?"C"; ????} ????[HttpGet("d")] ????public?async Task<string> D() ????{ ????????Thread.Sleep(new?Random().Next(1, 1500)); ????????return?"D"; ????} ????[HttpGet("e")] ????public?async Task<string> E() ????{ ????????Thread.Sleep(new?Random().Next(1, 1500)); ????????return?"E"; ????} } |
此時(shí)我們請(qǐng)求Demo1中的API A (xxx/api/consumer/a),然后在Kibana中打開APM中的Traces,找到”GET Consumer/A” 這條記錄(看起來(lái)默認(rèn)是根據(jù)Controller的名字+Action的名字命名的),然后點(diǎn)擊查看詳情。
在詳情中的最下方,我們找到TimeLine,可以看到如下圖所示的圖形:
我們可以看到我們?cè)谡?qǐng)求API A時(shí)的時(shí)間分別花費(fèi)在調(diào)用4個(gè)API中的時(shí)間,也可以看出調(diào)用第三個(gè)API花費(fèi)的時(shí)間更長(zhǎng),點(diǎn)擊藍(lán)色的條可以看到請(qǐng)求的詳細(xì)信息。
這里不太好的一點(diǎn)是默認(rèn)顯示的名字是GET localhost這樣的,其實(shí)我們更期望的是顯示成調(diào)用的api uri對(duì)吧?這個(gè)我提了一個(gè)pr給他們,大家可以關(guān)注下:https://github.com/elastic/apm-agent-dotnet/pull/463監(jiān)控EF執(zhí)行記錄
這個(gè)不需要過(guò)多的解釋,就是在EF執(zhí)行DB操作時(shí),進(jìn)行監(jiān)控,以便發(fā)現(xiàn)性能等問(wèn)題,我的代碼大致如下:
[HttpGet("person")] public?void?TestEfCore() { ????using?(var?db = new?ApmDbContext()) ????{ ????????var?jax = new?Person ????????{ ????????????Name = "西安.NET社區(qū)", ????????????Age = 26, ????????????Remark = "做最好的技術(shù)社區(qū)~" ????????}; ????????db.Persons.Add(jax); ????????db.SaveChanges(); ????????db.Persons.FirstOrDefault(x => x.Id == jax.Id ); ????????db.Persons.FirstOrDefault(x => x.Name == "西安.NET社區(qū)"); ????????jax.Name = ".NET西安社區(qū)"; ????????db.SaveChanges(); ????????db.Persons.Remove(jax); ????????db.SaveChanges(); ????} } |
當(dāng)我們使用Kibana查看這次請(qǐng)求時(shí),TimeLine顯示如下:
我們可以比較清晰直觀的看到在這次請(qǐng)求中,執(zhí)行了哪些SQL語(yǔ)句,各耗時(shí)多少,對(duì)我們的請(qǐng)求分析來(lái)說(shuō),還是蠻有用處的。點(diǎn)擊具體的藍(lán)條,還可以看到更詳細(xì)的數(shù)據(jù),但比較遺憾的是,數(shù)據(jù)中并沒(méi)有記錄SQL Params ,這對(duì)于我們想完全重現(xiàn)這次請(qǐng)求來(lái)說(shuō),還是不夠友好~
自行埋點(diǎn)記錄
相對(duì)來(lái)說(shuō),Elastic APM目前生態(tài)圈還不夠好,比sky walking還是稍微差一些組件的支持,如果要使用Elastic APM,免不了自己去做一些性能數(shù)據(jù)的埋點(diǎn)記錄,或者在為第三方組件、類庫(kù)做支持時(shí),也需要做一些數(shù)據(jù)的埋點(diǎn)。接下來(lái)我們就在我們的請(qǐng)求中,埋一些我們想額外記錄的信息,示例代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | [HttpGet] public?void?RecordMyApmData() { ????var?transaction = Agent.Tracer.CurrentTransaction; ????var?span1 = transaction.StartSpan("Stage 1", "Customize"); ????Thread.Sleep(300); ????span1.End(); ????Thread.Sleep(200); ????var?span2 = transaction.StartSpan("Stage 2", "Customize"); ????Thread.Sleep(100); ????span2.End();??????????? ????Thread.Sleep(100); ????var?span3 = transaction.StartSpan("Stage 3", "Customize"); ????Thread.Sleep(500); ????span3.End();??????????? } |
最終記錄的效果如下:
這個(gè)Demo雖然寫的很簡(jiǎn)單,但是我相信你已經(jīng)能大概腦補(bǔ)如何使用Elastic Apm Agent這個(gè)類去自定義自己需要捕捉的一些監(jiān)控?cái)?shù)據(jù)了~
異常監(jiān)控
當(dāng)我們的程序發(fā)生了異常時(shí),Elastic APM能幫助你記錄,這個(gè)功能和日志差不多,但可能比日志稍微好用那么一點(diǎn)點(diǎn)。我們一起來(lái)看看吧~
示例代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [HttpGet] public?void?TestException() { ????try ????{ ????????throw?new?Exception("捕獲的異常"); ????} ????catch?(Exception) ????{ ????} ????throw?new?Exception("未捕獲的異常"); } |
執(zhí)行代碼后,我們可以通過(guò)點(diǎn)擊Service Name,然后在Errors這個(gè)Tab頁(yè)中查看到這次的異常
點(diǎn)擊詳情,我們能看到詳細(xì)的堆棧調(diào)用信息:
此外,我們可以在Trasactions Tab中,找到發(fā)生異常的這個(gè)請(qǐng)求,然后點(diǎn)擊查看詳情,在詳情中我們也能看到這次異常的發(fā)生:
總結(jié)
本文介紹了如何使用Elastic APM在.NET Core應(yīng)用中收集性能和異常數(shù)據(jù),并使用Kibana進(jìn)行可視化分析,整體來(lái)說(shuō),Elastic APM還是挺強(qiáng)大的,對(duì)于性能監(jiān)控、鏈路追蹤、異常監(jiān)控基本是夠用了。
目前來(lái)說(shuō),Elastic APM 支持的組件還是比較有限,比如對(duì)數(shù)據(jù)庫(kù)查詢還只是支持EF Core,并不支持更多的組件,鏈路追蹤也僅支持HTTP請(qǐng)求的追蹤,也沒(méi)用支持其他的方式。另外,個(gè)人認(rèn)為Elastic APM把監(jiān)控報(bào)警(Watcher) 給放到X-Pack收費(fèi)包中也是挺讓人傷心的,異常監(jiān)控報(bào)警其實(shí)還是蠻關(guān)鍵的。
歡迎大家嘗試Elastic APM,有問(wèn)題的地方共同探討~
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的使用Elastic APM监控你的.NET Core应用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 编程语言这一年
- 下一篇: 试试这个Excel知识测验,得分超过80