基於Prometheus和Grafana打造業務監控看板

前言

業務監控對許許多多的場景都是十分有意義,業務監控看板可以讓我們比較直觀的看到當前業務的實時情況,然後運營人員可以根據這些情況及時對業務進行調整操作,避免業務出現大問題。

老黃曾經遇到過一次比較尷尬的「事故」。

其中一條業務線,服務着的其中一個商家,把大部分流量切到另外一個地方去了,而我們的運營人員在當天卻是完全不知情,第二天看了昨天的統計報表之後才發現這個商家的量少了很多,才能跟進協調處理。

ps: 當時實時報表比較欠缺,都是第二天凌晨生成昨天的數據報表,也沒有告警機制。

後面就弄了個大屏幕做了業務監控的實時看板,看一眼就知道有什麼風吹草動了。

先來看一下最終的效果圖。

這個圖裏面主要包含了下面幾個內容。

  1. 總的訂單數量
  2. 退單的數量
  3. 創建訂單的頻率
  4. 不同渠道的訂單量
  5. 不同渠道的退單量

下面就介紹一下如何實現這樣的業務監控。

搭建基礎設施

這裡涉及的基礎設施就有兩個,一個是prometheus,另一個是grafana。

先啟動prometheus,這裡直接用docker啟動。

$base = Split-Path -Parent $MyInvocation.MyCommand.Definition
$prometheusyml = Join-Path $base prometheus.yml
$fileconfig = Join-Path $base "config"

write-host $prometheusyml
write-host $fileconfig

docker run `
    --name prom `
    -p 9090:9090 `
    -v ${prometheusyml}:/etc/prometheus/prometheus.yml `
    -v ${fileconfig}:/etc/prometheus/fileconfig `
    prom/prometheus:v2.20.1

下面是prometheus.yml

global:
  scrape_interval:     15s 
  evaluation_interval: 15s
  
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093
      
rule_files:

scrape_configs:
  - job_name: 'file_ds'
    file_sd_configs:
    - refresh_interval: 10s
      files:
      - ./fileconfig/*.yml

這裡用了基於文件的發現機制,沒有用靜態的。更多其他方式,參見 //prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config

這個時候prometheus已經是運行起來了。

再來就是grafana了,啟動這個更加簡單。

docker run -d --name grafana -p 3000:3000 grafana/grafana:7.1.3

執行完,訪問 localhost:3000 就可以看到登錄界面了。

確定業務指標(metrics)

確定指標可以說是整個業務監控中最最最最最為主要的一個環節了,只有明確了我們要監控什麼,我們才可以在業務上去進行埋點,拿到想要的數據。

這個其實和我們平時面對的需求是一個樣的,需求明確了,做出來的東西才可能是我們想要的,需求不明確,做出來的東西可能就不會是我們想要的了。

為了幫助大家簡單的理解相關的內容,這裡舉個監控的例子,監控不同渠道的下單和退單量。

涉及到量的,在一天內基本上是屬於只增不減的,這個時候我們一般會選用 counter 類型來處理。

一個是下單,一個是退單,所以這裡定義兩個

  • yyyorder_created_total
  • yyyorder_canceled_total

counter類型的,一般在命名的時候最好都用_total作為結尾。

還有不同渠道呢?

渠道我們就用 lable 來標識一下。

最後展現格式大致如下:

yyyorder_created_total{appkey="mt",opreator="cw"} 1
yyyorder_canceled_total{appkey="pdd",opreator="cw"} 2

這裡也要注意一個問題,確定指標的時候,也要避免定義太多指標出來,如果可以,考慮用label去進行區分同性質的內容。

業務埋點

在明確了業務指標之後,就要在對應的業務上去進行埋點操作,會對業務代碼有一定的侵入性,當然如果業務代碼寫得足夠好,耦合的東西少,或許可以藉助AOP來埋點,從而降低侵入性。

後面就寫個簡單的例子來模擬業務埋點這一塊。

創建一個ASP.NET Core的項目,並安裝prometheus-net.AspNetCore這個nuget包。

<ItemGroup>
    <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
</ItemGroup>

其次是啟用 ASP.NET Core exporter middleware

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        // 這一句。
        endpoints.MapMetrics();
        endpoints.MapControllers();
    });
}

最後就是埋點了。

[ApiController]
[Route("")]
public class HomeController : ControllerBase
{
    private static readonly Counter OrderCreatedCount = Metrics
        .CreateCounter("yyyorder_created_total", "Number of created orders.", new CounterConfiguration
        {
             LabelNames= new [] { "appkey", "opreator" }
        });

    private static readonly Counter OrderCanceledCount = Metrics
        .CreateCounter("yyyorder_canceled_total", "Number of canceled orders.", new CounterConfiguration
        {
            LabelNames = new[] { "appkey", "opreator" }
        });

    [HttpGet]
    public string Get()
    {
        var appKeys = new[] { "ali", "pdd", "mt" };
        var opreators = new[] { "cw", "pz" };

        var rd = new Random((int)DateTimeOffset.Now.ToUnixTimeMilliseconds()).Next(0, 2000);
        var appKeyidx = rd % 3;
        var opreatoidx = rd % 2;
        OrderCreatedCount.WithLabels(appKeys[appKeyidx], opreators[opreatoidx]).Inc();

        var cRd = new Random((int)DateTimeOffset.Now.ToUnixTimeMilliseconds()).NextDouble();

        if (cRd < 0.3d)
        {
            OrderCanceledCount.WithLabels(appKeys[appKeyidx], opreators[opreatoidx]).Inc();
        }

        return "ok";
    }
}

上面這個控制器中,創建了兩個Counter,就是上面確定業務指標中定義好的。

這裡是每訪問一次,就創建一個訂單,同時生成一個隨機數,如果是小於0.3,那麼就當它是退單的,這樣就可以把兩種指標都模擬出來了。

程序剛啟動是有部分默認指標的。

當我們訪問埋點的地址後,可以發現我們自定義的業務指標也已經有數據了。

到這裡,數據已經有了,我們要怎麼呈現呢?

要想呈現數據,我們需要先讓prometheus來保存我們的業務指標數據。

數據寫入

把數據寫入prometheus有兩鍾方式,一種是pull,一種是push。

pull是讓prometheus主動去拉取我們產生的數據,只要我們暴露一個地址出來即可,這中也是比較推薦的做法。

push方式要藉助pushgateway,埋點數據要先主動推送到pushgateway,後面在由pushgateway把數據寫進prometheus。

默認情況下,當我們用了endpoints.MapControllers();之後,就會把數據暴露在 //ip:port/metrics 這個地址上。

知道要用pull的方式後,還要做什麼呢?當然就是要去配置promethues了。

前面我們的 scrape_configs 是通過文件去自動發現的,所以只要在掛載的路徑上面加一個對應的yml文件就可以了。

老黃這裡加了一個nc-service.yml,具體內容如下:

- labels:
    service: nc
    project: demo
  targets: 
  - 192.168.1.103:9874
  - 192.168.1.103:9875

這個時候就可以在Targets裏面看到我們這兩個地址的信息了。

通過prometheus的默認界面,也可以發現數據已經正常讀取了。

後面就是真正的數據查詢和展示了。

數據展示

通過上面的步驟,我們已經保證數據可以正常寫入和查詢了,現在就在grafana中創建一個業務監控看板了。

在grafana中先配置我們的數據源。

這裡填上我們prometheuse的地址保存就可以了,可以看到那個綠色的提示,告訴我們這個數據源是正常工作的了。

先來一個總的訂單數。

創建一個新的dashboard,再創建一個Panel。

我們在panel中填寫我們的信息還有就是選擇要的圖形。

然後就是寫上查詢條件,就可以看到我們要的結果了。

訂單總數這個查詢如下:

sum(ceil(increase(yyyorder_created_total[1d])))

裏面用到了, sum、ceil、increase這三個函數。

其中 increase 是用來統計一段時間範圍內的增量。後面帶了 [1d] 這個範圍表明這裡是查看1天內的增量。

ceil是用來把increase的結果進行四捨五入的,可能有人會好奇,怎麼還會要四捨五入呢?

看看下面這個圖,大家就會發現,非常多的小數點。

其實這個和prometheus的統計方法是有關係的,這裡不展開,先這樣用着。

sum 這個是用來求和的,指標中還包含了很多label,我們還要把每個label的求和,才是真正的結果。

所以這裡就得到了下面這個結果。

退單總數的查詢和訂單總數一樣,只是把名字換成退單的即可。

sum(ceil(increase(yyyorder_canceled_total[1d])))

再來看看各渠道的訂單統計。

既然是看各渠道的統計,那麼這裡就要用到前面定義好的label了。appkey代表的就是渠道,那麼我們基於它去分一下組就可以了。

就得到下面的查詢。

sum by (appkey) (ceil(increase (yyyorder_created_total[1d])))

結果如下:

同理,各渠道退單的也是一樣的寫法

sum by (appkey) (ceil(increase (yyyorder_canceled_total[1d])))

ps: 如果想把訂單和退單的放在一個圖裏面,可以加多個查詢。

示例如下:

現在有了所有渠道的總量,各個渠道獨立的總量,那麼我們有辦法知道某個時間段的趨勢嗎?

這個是肯定有的,且聽老黃慢慢道來。

有上面這個疑問,多半是經歷過,某個時間段量非常多,但是有的時間段又幾乎為零,玩的就是心跳呀。

我們可以把這個稱之為時間段內的訂單增長情況。

這裡需要用到rate函數,這個就是幫助我們統計增長速率的函數。

它統計的是每秒的平均增長率,這個粒度有點太細,所以我們會在這個基礎上乘以60,放大到一分鐘。

然後在看它sum的結果,最後才四捨五入。

ceil(sum(rate(yyyorder_created_total[5m]) * 60))

結果如下:

從這個結果可以看出,訂單大部分時間都沒有增長,只在中間那個時間段,有部分單進來。

到這裡主要的各個小panel已經完成了,剩下的就是在dashboard裏面調整位置,大小那些了。

總結

這樣打造出來的監控看板還是挺不錯的,不過還是要注意下面幾個問題的

  1. prometheus是把數據存儲在本地的,總是會達到上限的,這裡要麼是定期刪,要麼是寫到遠程存儲。
  2. prometheus自己獨立的查詢語法可能剛開始會比較不適應,查不出自己想要的結果,這裡多查查資料,多實踐基本問題也不大。
  3. 業務埋點這一個塊,還是要儘可能的減少對現有業務代碼的侵入性。
  4. 業務指標一定要確定好,不然埋點痛苦,查詢也痛苦。

這裡還沒有涉及到告警相關的內容,後面有時間再寫一個告警相關的。

文中示例代碼:

//github.com/catcherwong-archive/2020/tree/master/08/PromDemo

本文首發於我的個人公眾號 不才老黃 ,不定期發佈一些內容,有興趣的可以關注一下喲!