1. 程式人生 > >使用Elastic APM監控你的.NET Core應用

使用Elastic APM監控你的.NET Core應用

作者:Jax

前言

在應用實際的運維過程中,我們需要更多的日誌和監控來讓我們對自己的應用程式的執行狀況有一個全方位的瞭解。然而對於大部分開發者而言,平時大家所關注的更多的是如何更優雅的實現業務,或者是如何讓應用的響應速度更快等等與編碼相關的技術,對於應用程式的監控,可能還停留在日誌檔案的層面,而且大多數是出了事故被人為發現後,才通過日誌嘗試去定位問題。

本文所準備介紹的Elastic APM是一套用於監控應用各項指標,比如系統響應時間、異常、EF執行的SQL記錄等等,並且可以將這些記錄組織成一個可追溯的鏈路,方便查詢問題。此外,Elastic APM還可以通過Kibana來做非常漂亮的視覺化展示,方便我們定位和發現問題。

廢話不再多說,我們開始實戰~

Elastic APM介紹

Elastic APM的由下面四個元件所組成,如下圖:

APM Agent

APM Agent是安裝到你的.NET Core程式中的一個Nuget包,他用於效能、錯誤等各類資料的收集,並將收集到的資料快取起來分批發送到APM Server。當然,除了.NET Core使用的Nuget包,他還可以支援很多其他的語言,比如Java,Node.Js,Python等

支援的語言列表請參考這裡:https://www.elastic.co/guide/en/apm/agent/index.html

APM Server

APM Server是部署在伺服器端的一個用於接收Agent發來的資料包的應用程式,並根據這些資料包自動建立文件,將資料轉存到Elastic Server中。

Elastic Search

這個相信大家都很熟悉了,他就是一個基於Lucene實現的高效能、分散式的全文搜尋引擎,用於快速、實時的儲存、搜尋和分析大量資料。在這裡來說,他提供的是資料儲存和搜尋能力!

Kibana

如果你熟悉Elastic Search,那麼你一定多少會了解Kibana,Kibana是開源的分析和視覺化平臺,他能與Elastic Search進行很好的協同,幫助你快速的視覺化儲存在Elastic Search中的資料,並做成各種各樣漂亮的報表、圖形等。

環境準備

在本次的實戰過程中,我們需要以下的東西:

  • Elastic Search
  • Kibana
  • APM Server
  • 一個基於.NET Standard 2.0 + 的專案

Elastic Search的安裝:https://www.cnblogs.com/baiyunchen/p/11227144.html

Kibana的安裝:

我的環境是Centos 7,所以照著https://www.elastic.co/guide/en/kibana/7.3/rpm.html 這個官網教程安裝的,整個過程很簡單:

  • 下載Kibana RPM包(採用這種方式是因為用yum install網速太慢,所以我用迅雷下載完成rpm檔案後上傳到Linux機器中)
  • 執行命令rpm --install  “下載的檔名” 進行安裝
  • 安裝完成後,到/etc/kibana/kibana.yml檔案中在檔案末尾增加以下配置:
server.host: 0.0.0.0
server.name: 主機IP
server.port: 一個你喜歡的埠號
elasticsearch.hosts: ["已安裝好的ES地址,多個之間用逗號隔開"]
logging.dest: /var/log/kibana.log //需要提前把這個檔案建立好,然後把許可權給夠
  • 將Kibana安裝為系統服務並啟動
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable kibana.service

sudo systemctl start kibana.service

這裡大家一定要注意Elastic Search的版本和Kibana一定要匹配,不然會報錯的。(我的ES是前段時間裝的,所以會有這問題,如果大家一口氣安裝所有的,應該沒啥問題)

如果不幸遇到了問題,可以通過配置檔案中logging.dest中配置的路徑檢視日誌。

APM Server的安裝

APM Server的安裝跟Kibana的安裝類似,過程如下:

  • 下載RPM包,包在這個頁面找你需要的版本,也需要跟ES、Kibana的版本一致,不然你懂得~ https://www.elastic.co/cn/downloads/past-releases#apm-server
  • 執行rpm --install “下載的檔名”進行安裝
  • 在資料夾/etc/amp-server中修改配置檔案apm-server.yml,將配置檔案最開始的host: “localhost:8200”修改成“0.0.0.0:8200”,以便讓他能允許通過ip:埠號的方式訪問, 並在配置的最後面新增如下配置:
output.elasticsearch:
    hosts: ["已安裝好的ES地址,多個之間用逗號隔開"]
  • 將apm-server安裝為系統服務並啟動
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable apm-server.service

sudo systemctl start apm-server.service

執行上述操作完成後,在瀏覽器中嘗試開啟伺服器Ip:8200,最終如果APM Server安裝的沒有問題,則瀏覽器中會打印出類似於如下的內容:

{
  "build_date": "2019-06-20T14:39:23Z",
  "build_sha": "9a099b63c53eac8c55707df96193143ec66337e9",
  "version": "7.2.0"
}

此時我們在瀏覽器中開啟Kibana,然後點選Add APM

然後將新開啟的頁面往下滾動,點選Check APM Server Status按鈕,如果出現You have correctly setup APM Server則說明安裝完成~

到這裡為止,我們的安裝工作就全部完成了,接下來,我們嘗試將.NET Core與Elastic APM整合起來,一起繼續吧~

.NET Core 應用整合

我們建立一個Demo專案,來用於測試APM的各項功能。

專案的地址請參考GitHub:

引用依賴包

我們需要從Nuget引用相關的SDK來與我們的應用做整合,其實就是引用我們最開始說的APM Agent的部分,在Nuget中,我們引用Elastic.Apm.NetCoreAll這個包。

依賴這個包其實相當於自動依賴瞭如下三個包,你也可以根據需要只依賴其中的一部分。

  • Elastic.Apm
  • Elastic.Apm.AspNetCore
  • Elastic.Apm.EntityFrameworkCore

這裡我們為了簡單起見,直接印用Elastic.Apm.NetCoreAll這個包

將Agent新增到.NET Core

找到.NET Core的StartUp檔案,在裡面的Configure方法中新增如下程式碼:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAllElasticApm(Configuration);
}

然後在application.json中新增如下內容:

{
  "ElasticApm": {
    "LogLevel": "Error", // Log級別,根據自己的需要來定"ServerUrls": "http://localhost:8200", //設定前面安裝好的APM Server URL,預設埠號是8200
    "ServiceName" : "MyApp", //應用的名字,跟著實際情況起就行,allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application
  }
}

此時我們將專案啟動起來,隨便的重新整理幾下,然後回到Kibana中,在剛才的頁面中往下滾動,選擇.NET,然後點選Check Agent Status按鈕,如果順利,就會顯示“Data successfully received from one or more agents”,如果不幸沒能顯示這句話,可以通過VS的Diagnostic Tools中的Event跟蹤一下,看看是不是哪裡沒有配置對

 

 

監控資料檢視

在Kibana的Add APM頁面的最下方,找到Load Kibana Objects,來建立索引,然後點選APM dashboard按鈕,就可以進入APM資料的檢視頁面。

 

點選APM Dashboard按鈕後,展示的頁面如下:

該頁面中的功能分為Services、Traces兩個大的功能模組,先來簡單瞭解一下這兩個Tab頁中對應的功能。

Services

下面的列表中顯示的XianDotnetCommunity其實就是你在配置檔案中配置的ServiceName,點選這個名字進入,又可以看到如下的報表,裡面有Transactions,Errors,Metrics三個Tab頁。

 

 

其中

Transactions:展示的當前應用請求情況的概覽,包括請求響應時長、請求呼叫次數等等

Errors:程式中的異常列表

Metrics:應用程式所在機器的CPU/記憶體使用情況

PS:其實我覺得非常需要一個當前應用程式所消耗的記憶體和CPU的值,但是貌似.NET Core版本的代理沒有實現這些功能,期待未來的更新吧

Traces

裡面是用於做鏈路追蹤的檢視,首頁包含所有事務的名稱列表以及響應時間等

點選具體的事務進去,可以看到這個事務經過的鏈路列表以及更詳細的一些響應資訊,從而幫你分析出整個鏈路中的瓶頸,更多內容我們在下面細講。

探索更多

Elastic APM還有很多其他的功能,比如鏈路追蹤、資料庫呼叫執行,讓我們來一起探索吧~

監控API呼叫鏈路追蹤

如果你瞭解過微服務架構,那你一定了解鏈路追蹤這個概念。那什麼是鏈路追蹤呢?舉個栗子:

有個服務A,他會依賴服務B,C,而服務B又會依賴服務D,E,服務C又依賴F,G(省略無數依賴關係),然後有一天,服務A變得非常慢,那到底該怎麼定位是哪個服務慢呢?此時鏈路最終就派上用場了~

我們來簡單模擬一下這種巢狀的呼叫:

在一個WebAPI專案Demo1中有一個ConsumerController,他裡面有一個API A,裡面呼叫了另外一個WEB API專案Demo2中的介面B/C/D/E。程式碼大致如下:

專案甲:

[Route("api/consumer")]
[ApiController]
public class ConsumerController : ControllerBase
{
    private readonly IHttpClientFactory _httpClientFactory;
    public ConsumerController(IHttpClientFactory httpClientFactory)
    {
        //使用HttpClientFactory時需要先在StartUp中呼叫services.AddHttpClient();
        _httpClientFactory = httpClientFactory;
    }

    private const string baseUri = "http://localhost:54597";


    [HttpGet("a")]
    public async Task<string> A()
    {
        //HttpClient client = new HttpClient();
        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={b} & c={c} & d={d} & e={e}";
    }
}

專案乙:

[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";
    }
}

此時我們請求Demo1中的API A (xxx/api/consumer/a),然後在Kibana中開啟APM中的Traces,找到”GET Consumer/A” 這條記錄(看起來預設是根據Controller的名字+Action的名字命名的),然後點選檢視詳情。

在詳情中的最下方,我們找到TimeLine,可以看到如下圖所示的圖形:

我們可以看到我們在請求API A時的時間分別花費在呼叫4個API中的時間,也可以看出呼叫第三個API花費的時間更長,點選藍色的條可以看到請求的詳細資訊。

這裡不太好的一點是預設顯示的名字是GET localhost這樣的,其實我們更期望的是顯示成呼叫的api uri對吧?這個我提了一個pr給他們,大家可以關注下:https://github.com/elastic/apm-agent-dotnet/pull/463

監控EF執行記錄

這個不需要過多的解釋,就是在EF執行DB操作時,進行監控,以便發現效能等問題,我的程式碼大致如下:

[HttpGet("person")]
public void TestEfCore()
{
    using (var db = new ApmDbContext())
    {
        var jax = new Person
        {
            Name = "西安.NET社群",
            Age = 26,
            Remark = "做最好的技術社群~"
        };

        db.Persons.Add(jax);

        db.SaveChanges();

        db.Persons.FirstOrDefault(x => x.Id == jax.Id );

        db.Persons.FirstOrDefault(x => x.Name == "西安.NET社群");

        jax.Name = ".NET西安社群";
        db.SaveChanges();

        db.Persons.Remove(jax);
        db.SaveChanges();
    }
}

當我們使用Kibana檢視這次請求時,TimeLine顯示如下:

我們可以比較清晰直觀的看到在這次請求中,執行了哪些SQL語句,各耗時多少,對我們的請求分析來說,還是蠻有用處的。點選具體的藍條,還可以看到更詳細的資料,但比較遺憾的是,資料中並沒有記錄SQL Params ,這對於我們想完全重現這次請求來說,還是不夠友好~

自行埋點記錄

相對來說,Elastic APM目前生態圈還不夠好,比sky walking還是稍微差一些元件的支援,如果要使用Elastic APM,免不了自己去做一些效能資料的埋點記錄,或者在為第三方元件、類庫做支援時,也需要做一些資料的埋點。接下來我們就在我們的請求中,埋一些我們想額外記錄的資訊,示例程式碼如下:

[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();            
}

最終記錄的效果如下:

這個Demo雖然寫的很簡單,但是我相信你已經能大概腦補如何使用Elastic Apm Agent這個類去自定義自己需要捕捉的一些監控資料了~

異常監控

當我們的程式發生了異常時,Elastic APM能幫助你記錄,這個功能和日誌差不多,但可能比日誌稍微好用那麼一點點。我們一起來看看吧~

示例程式碼如下:

[HttpGet]
public void TestException()
{
    try
    {
        throw new Exception("捕獲的異常");
    }
    catch (Exception)
    {

    }

    throw new Exception("未捕獲的異常");
}

執行程式碼後,我們可以通過點選Service Name,然後在Errors這個Tab頁中檢視到這次的異常

點選詳情,我們能看到詳細的堆疊呼叫資訊:

此外,我們可以在Trasactions Tab中,找到發生異常的這個請求,然後點選檢視詳情,在詳情中我們也能看到這次異常的發生:

總結

本文介紹瞭如何使用Elastic APM在.NET Core應用中收集效能和異常資料,並使用Kibana進行視覺化分析,整體來說,Elastic APM還是挺強大的,對於效能監控、鏈路追蹤、異常監控基本是夠用了。

目前來說,Elastic APM 支援的元件還是比較有限,比如對資料庫查詢還只是支援EF Core,並不支援更多的元件,鏈路追蹤也僅支援HTTP請求的追蹤,也沒用支援其他的方式。另外,個人認為Elastic APM把監控報警(Watcher) 給放到X-Pack收費包中也是挺讓人傷心的,異常監控報警其實還是蠻關鍵的。

歡迎大家嘗試Elastic APM,有問題的地方共同探討~