Prometheus 為你的系統(tǒng)保駕護航

“You can’t manage what you don’t measure”
— W. Edwards Deming

近年來,以docker 為首的容器技術(shù)在IT領(lǐng)域尤其是在云計算和微服務(wù)應(yīng)用領(lǐng)域掀起了一股狂潮,成為當(dāng)下特別流行的一種技術(shù),說明容器技術(shù)正好滿足了當(dāng)今軟件領(lǐng)域中的一些迫切需求,它主要解決了下面的一些問題

  • 它將應(yīng)用及依賴的運行環(huán)境打包成鏡像,消除了線上線下環(huán)境的差異,保證了應(yīng)用環(huán)境的一致性;

  • 作為一種輕量級的虛擬化技術(shù),它以很小的代價卻提供了不錯的資源隔離和限制能力,可以更加細粒度的分配資源,極大提高了資源的使用率;

  • 還有它構(gòu)建一次,到處運行,提高了容器的跨平臺性的同時,大大簡化了持續(xù)集成、測試和發(fā)布的過程……

容器監(jiān)控的解決方案

在容器時代,傳統(tǒng)的那些耳熟能詳?shù)谋O(jiān)控軟件如Zabbix 不能提供方便的容器化服務(wù)的監(jiān)控體驗,相反的許多新生的開源監(jiān)控項目則將對容器監(jiān)控的支持放到了關(guān)鍵特性的位置,如InfluxDB ,Prometheus 等獲得了廣泛的認可。

在DockerCon EU 2015上,Swisscom AG的云方案架構(gòu)師Brian Christner闡述了“Docker監(jiān)控”的概況,分享了這方面的最佳實踐和Docker stats API的指南,并對比了三個流行的監(jiān)控方案:cAdvisor、“cAdvisor + InfluxDB + Grafana”以及Prometheus。其中Prometheus是整體化的開源監(jiān)控軟件,但它本身對容器信息的收集能力以及圖表展示能力相比其他專用開源組件較弱,通常在實際實施的時候依然會將它組合為『cAdvisor + Prometheus』或『cAdvisor + Prometheus + Grafana』的方式使用。

Prometheus作為天生的容器監(jiān)控的項目,特別適合作為度量數(shù)據(jù)的存儲和查詢,所以我們選擇了以Prometheus為核心的容器監(jiān)控系統(tǒng)的解決方案。

我們后端大量采用Java 和Node.js 兩種開發(fā)語言,Java 后端采用Spring Cloud 支撐的一套微服務(wù)架構(gòu),我們需要將開源的監(jiān)控軟件與本公司的實際技術(shù)相結(jié)合。坐而言不如起而行,接下來我們會介紹監(jiān)控系統(tǒng)的搭建和使用、Prometheus基礎(chǔ)和自定義監(jiān)控數(shù)據(jù)詳細介紹『cAdvisor + Prometheus + Grafana』這種解決方案在本公司的本土化過程。

監(jiān)控組件

一般性能監(jiān)控系統(tǒng)會包含5大組件:

  • 探針:安裝在應(yīng)用中收集應(yīng)用性能的包
  • 收集器:收集探針發(fā)送過來的數(shù)據(jù)或者主動拉取應(yīng)用性能數(shù)據(jù)的工具
  • 存儲介質(zhì):存儲收集到的應(yīng)用性能數(shù)據(jù)的介質(zhì)
  • 展示器:將應(yīng)用性能數(shù)據(jù)按照使用者的要求展示的工具
  • 預(yù)警器:當(dāng)某一監(jiān)測值超過預(yù)定閾值向devops成員發(fā)出預(yù)警

監(jiān)控組件簡要介紹

prometheus

Prometheus是一個開源的時序數(shù)據(jù)收集和處理的服務(wù)軟件,它其實包含監(jiān)控組件中的:收集器,存儲和展示器,它所需的探針有相應(yīng)的exporter和client library exporter組件提供,預(yù)警器可以由配套的Alertmanager 軟件提供,它既可以監(jiān)控各個主機的CPU、內(nèi)存、文件等資源的使用情況,也可以監(jiān)控服務(wù)的健康狀況、網(wǎng)絡(luò)流量,它既可以監(jiān)控mysql、redis的使用狀況,也可以定制監(jiān)控信息,它是一個整體化解決方案的監(jiān)控軟件。

cAdvisor

cAdvisor會收集、聚集、處理并導(dǎo)出運行中容器的信息,它可以為容器用戶提供了了解運行時容器資源使用和性能特征的方法,安裝后你可以通過web界面直接看到相應(yīng)機器上容器中應(yīng)用的資源使用和性能特征。它包含了監(jiān)控組件中的前4大組件,但是它只能監(jiān)控容器資源的使用和性能,因為它叫container advisor。

Grafana

Grafana是一個開源的領(lǐng)先的能非常漂亮的展示時序數(shù)據(jù)分析結(jié)果的軟件。它是使用js所寫的一個軟件,支持多種數(shù)據(jù)源,支持多種圖形面板展示,它也提供了預(yù)警功能,是一個非常棒的圖形展示組件。

exporter

下面介紹的是各種度量數(shù)據(jù)的 exporters

  • node_exporter: 監(jiān)控一個主機的CPU、內(nèi)存等資源的使用狀況

  • blackbox_exporter:監(jiān)控各個服務(wù)的健康狀況

  • mysqld_exporter: 監(jiān)控mysql的使用狀況

  • redis_exporter: 監(jiān)控redis的使用狀況

  • SNMP_exporter: 監(jiān)控網(wǎng)絡(luò)流量

到現(xiàn)在為止我們已經(jīng)將整個容器監(jiān)控系統(tǒng)搭建起來,我們的監(jiān)控系統(tǒng)具有監(jiān)控各個服務(wù)器CPU、內(nèi)存、文件等資源使用狀況,服務(wù)器中每個容器的CPU、內(nèi)存、文件等資源使用狀況以及對我們開發(fā)的應(yīng)用進行健康檢查的能力。我們做的非常的完美,是的,我們開發(fā)的應(yīng)用終于可以上線了。這一切看似完美的背后仍然存在一點小小的缺憾,比如我想知道各個微服務(wù)的被調(diào)用情況,每次請求的延遲,每次請求正確與否,一段時間內(nèi)的請求量……;仔細觀察,我們還會發(fā)現(xiàn)一個問題,就是在Prometheus 的配置文件中我們監(jiān)控的地址都是固定的,我們知道在docker容器集群中,這些地址是隨時變動的,還有我們增加一個服務(wù)就需要去更改Prometheus 的配置文件,that is too bad!我需要Prometheus 可以動態(tài)更改監(jiān)控的地址,動態(tài)增加監(jiān)控的服務(wù),we need more!

Prometheus的數(shù)據(jù)模型

Prometheus從目標(biāo)主機或者從push-gateway 拉取數(shù)據(jù),存在本地文件系統(tǒng)Alertmanager 根據(jù)recordRules配置文件計算聚合數(shù)據(jù)或者根據(jù)alertRules 計算是否發(fā)送預(yù)警。Grafana可以直接使用Prometheus 提供的Api 發(fā)送promQL查詢時序數(shù)據(jù)繪圖。

Prometheus將相同的 metrics(指標(biāo)名稱) 和 labels(一個或多個標(biāo)簽) 組成一條時間序列

metrics 一般是給監(jiān)測對象起一個名字。

labels 一般是給監(jiān)測對象提供一些額外信息的鍵值對,對一條時間序列不同維度的識別,promQL將通過這些標(biāo)簽很容易的過濾和聚合這些時間序列數(shù)據(jù)。

api_http_requests_total{method="POST", handler="/messages"}

存入數(shù)據(jù)庫中時還會自動為它添加一個時間戳標(biāo)記,所以一個時序序列是大量不同時間的相同指標(biāo)相同標(biāo)簽的數(shù)據(jù)集合。
如果以傳統(tǒng)數(shù)據(jù)庫的理解來看這條語句,則可以考慮 http_requests_total是表名,標(biāo)簽是字段,而timestamp是主鍵,還有一個float64字段是值了。(Prometheus里面所有值都是按float64存儲)

Prometheus的指標(biāo)類型

Prometheus的客戶端軟件包中提供了4 種核心的指標(biāo)類型,這四種類型僅僅在客戶端存在區(qū)別,在服務(wù)端存儲時轉(zhuǎn)換為無類型的時間序列。

  • Counter
    累加器或者稱作計數(shù)器,統(tǒng)計的指標(biāo)值只能增加,不能減少,增加值不一定為1,可以用于請求的總數(shù)、訪問時間的總和。
  • Gauge
    指數(shù)器,指示當(dāng)前統(tǒng)計的指標(biāo)值的大小,值可以增大也可以減小,主要用戶統(tǒng)計當(dāng)前cpu 的溫度、最近一次訪問的耗時。
  • Histogram
    直方圖,統(tǒng)計指標(biāo)值分散在不同區(qū)間的個數(shù)。相當(dāng)于針對Gauge 做了一次再加工,統(tǒng)計的時候就將分散在不同區(qū)間的個數(shù)統(tǒng)計好了。比如統(tǒng)計每次訪問耗時的數(shù)據(jù)分布情況,用Histogram 可以統(tǒng)計小于200 ms的訪問次數(shù),小于300 毫秒的次數(shù),小于500 毫秒的次數(shù)等等
  • Summary
    概述,它的作用我們通過一個例子來說明:比如我們監(jiān)測的指標(biāo)值為每次請求的響應(yīng)時間,用Summary 可以統(tǒng)計5min 內(nèi)95% 的請求的響應(yīng)平均用時,5min 內(nèi)80% 的請求的響應(yīng)用時……。我們也可以統(tǒng)計10 min內(nèi)60% 的請求的響應(yīng)的平均用時……其實Summary 也是針對Counter 或者 Gauge 做的再次加工,只是在記錄到數(shù)據(jù)庫之前它計算好了再存入數(shù)據(jù)庫。它和Histogram 針對同一監(jiān)測指標(biāo)的區(qū)別是Summary 將次數(shù)作為橫坐標(biāo), Histogram 是將次數(shù)作為縱坐標(biāo)。

通過上面的介紹我們知道最基本的類型其實就是Counter 和Gauge 兩種,其他的類型都是在它們基礎(chǔ)上的再加工。了解了這4中類型,我們才能選擇正確的類型統(tǒng)計我們需要監(jiān)測的數(shù)據(jù)

promQL基礎(chǔ)

我們使用過sql,我們都能感受到sql語言查詢功能的強大之處,promQL的查詢功能也異常強大,它也具有運算、過濾、分組等功能,它還提供大量的內(nèi)置函數(shù),可以讓我們更加容易對時序數(shù)據(jù)進行操作。當(dāng)我們學(xué)會了promQL我們就能很順利的將我們想要展示的數(shù)據(jù)按照我們的要求完美的展示出來。
promQL是非常簡單的,在開始學(xué)習(xí)promQL之前我們先看一些簡單的promQL查詢表達式。

栗子

http_requests_total

返回監(jiān)測指標(biāo)名為http_requests_total時間戳為當(dāng)前時間的時序序列(它們的標(biāo)簽可能不同,所以結(jié)果可能有多條)

http_requests_total{job="apiserver", handler="/api/comments"}

返回標(biāo)簽job=”apiserver”, handler=”/api/comments”,監(jiān)測指標(biāo)名為http_requests_total時間戳為當(dāng)前時間的時序序列。大括號相當(dāng)于sql 的 where

http_requests_total{job="apiserver", handler="/api/comments"}[5m]

返回5分鐘前到當(dāng)前時間的標(biāo)簽為job=”apiserver”, handler=”/api/comments”,監(jiān)測指標(biāo)名為http_requests_total的時序序列。中括號相當(dāng)于對時間加了一個維度的限制

http_requests_total{job=~"server$"}

返回標(biāo)簽job的值為以server結(jié)尾的指標(biāo)名為http_requests_total的時序序列。=后面接一個正則表達式,表示此標(biāo)簽的值匹配后面的正則表達式,那么!就是不匹配后面的正則

http_requests_total offset 5m

返回的是5分鐘之前的請求總數(shù)

下面會介紹4個函數(shù)的使用

rate(http_requests_total[5m])

http_requests_total記錄的是請求的總數(shù)的時序序列,http_requests_total[5m]記錄的是5分鐘內(nèi)請求的總數(shù)的時序序列,rate是計算平均率的函數(shù)既計算每秒的平均增加數(shù)。所以這個promQL的作用是計算5分鐘內(nèi)平均每秒的請求數(shù)。如果我們記錄指標(biāo)有三種,

http_requests_total{instance=“127.0.0.1”,job="prometheus"}
http_requests_total{instance=“127.0.0.2”,job="prometheus"}
http_requests_total{instance=“127.0.0.2”,job="monitor"}

那么結(jié)果rate(http_requests_total[5m])得到的結(jié)果也是三種,因為他們的標(biāo)簽不一樣,屬于3種時間序列,所以我們結(jié)果會有3種。如果我想得到這三個時間序列的每秒總請求數(shù)則可以

sum(rate(http_requests_total[5m]))

sum()是求和函數(shù),可以將不同維度的時間序列聚合,得到的結(jié)果只有一種時間序列?,F(xiàn)在我想將127.0.0.1和127.0.0.2這兩個實例的請求數(shù)分別聚合既我想得到每臺機器的每秒請求總數(shù),可以使用promQL的分組功能

sum(rate(http_requests_total[5m])) by (instance)

這樣我們得到的結(jié)果又兩種時間序列?,F(xiàn)在我想得到3中時間序列中每秒請求數(shù)最多的一個該怎么計算呢?如下

topk(1,http_requests_total[5m])

如果我想知道http_requests_total有多少種指標(biāo),可以

count(http_requests_total)

結(jié)果返回值為3

promQL高級

我們已經(jīng)對promQL已經(jīng)有了一定程度的了解,但是如果我們想使用的更加得心應(yīng)手,則還需要對promQL有更加深入的了解。

數(shù)據(jù)類型

在Prometheus中有3種數(shù)據(jù)類型

  • Instant vector 即時向量,你可以看一個時間點的時序序列,它反映的是一個時間點不同標(biāo)簽的值組成的時間序列,如
http_requests_total
  • Range vector 范圍向量,你可以看做帶有中括號時間限制的時序序列,它反映的是一段時間范圍內(nèi)的值組成的時間序列,時間單位可以是:s、m、h、d、w、y,如
rate(http_requests_total[5m])
  • Scalar 標(biāo)量,你可以把它看成一個float64位的數(shù)值

隱式標(biāo)簽

http_requests_total = {__name__ = 'http_requests_total'}

以上2個查詢表達式是等價的

向量匹配

promQL中的數(shù)據(jù)類型是可以相互運算的即可以+,-,*,/……,它們的運算分為3種

  • 標(biāo)量 oprator 標(biāo)量
  • 向量 oprator 標(biāo)量
  • 向量 oprator 向量

前面2種我們是很容易理解的,第3種它內(nèi)部是如何計算的呢?接著基礎(chǔ)篇的例子

http_requests_total - http_requests_total offset 5m

這個例子返回的是5分鐘內(nèi)請求的數(shù)量,返回的結(jié)果又幾種呢?3種。因為它是根據(jù)標(biāo)簽來進行匹配的,即操作符兩邊的標(biāo)簽完全相同(不包括隱式標(biāo)簽,以__開頭)的兩個時間序列才進行運算。

自定義監(jiān)控數(shù)據(jù)

需求是永無止境的,我們一定要在正確的時間做正確的事情。Prometheus 作為容器監(jiān)控系統(tǒng),它的功能是強大的,我們可以通過它提供的客戶端開發(fā)包,自定義我們需要監(jiān)控的性能數(shù)據(jù);我們也可以通過它提供的file_sd_configs 動態(tài)配置監(jiān)控的地址,動態(tài)增加監(jiān)控的服務(wù),這正好解決了前面章節(jié)我們提出的2 個明確的需求。下面就讓我們開啟這一場神奇的解決問題和coding 之旅吧。

自定義監(jiān)控數(shù)據(jù)的目標(biāo)

我們自定義監(jiān)控數(shù)據(jù)的目標(biāo)是在15s 內(nèi)發(fā)現(xiàn)系統(tǒng)的穩(wěn)定性問題并主動的去解決問題。其實就是在最短的時間內(nèi)發(fā)現(xiàn)由于應(yīng)用發(fā)布、應(yīng)用內(nèi)部bug、應(yīng)用崩潰自重啟等等帶來的系統(tǒng)的穩(wěn)定性問題。最終我們搭建好的監(jiān)控系統(tǒng)在實際的生產(chǎn)環(huán)境中是15s 去拉取一次度量指標(biāo),這個時間最短可以調(diào)到1s 一次,所以說我們的系統(tǒng)是一個準(zhǔn)實時或者說是一個實時的監(jiān)控系統(tǒng),它帶來解決問題的一個巨大的革新。

我們后端的微服務(wù)采用的是SpringCloud 支撐的微服務(wù)架構(gòu),我們的網(wǎng)關(guān)為Zuul ,所以任何請求都會經(jīng)過Zuul來做轉(zhuǎn)發(fā),我們監(jiān)控的思路是,在Zuul 網(wǎng)關(guān)層增加一個過濾器,記錄每個請求的模塊,每個請求的路徑,每個請求的方法,每個請求的響應(yīng)時間,每個請求的次數(shù)。然后將這些數(shù)據(jù)導(dǎo)入到Prometheus,Grafana 再將Prometheus 中的數(shù)據(jù)以圖表的形式展示出來。

網(wǎng)關(guān)里自己實現(xiàn)一個filter 記錄下面三種數(shù)據(jù)格式, 也可以考慮使用 spring boot actuator

http_response_time_milliseconds_count{method="ytx_sso_users",module="ytx_sso",status="200",method_type="GET",} 166.0  
http_response_time_milliseconds_sum{method="ytx_sso_users",module="ytx_sso",status="200",method_type="GET",} 110186.0  
http_total_request_size{status="200",module="cms_content",} 2162.0

http_response_time_milliseconds_count統(tǒng)計每個接口的調(diào)用次數(shù)
http_response_time_milliseconds_sum統(tǒng)計每個接口響應(yīng)時間的總和
http_total_request_size統(tǒng)計每個模塊的調(diào)用總次數(shù)
以上三種都是Counter類型,前兩種通過Summarry統(tǒng)計的,最后一種使用Counter統(tǒng)計的。
labels標(biāo)簽:method記錄訪問的路徑,module記錄訪問的模塊,status記錄響應(yīng)的狀態(tài)碼,method_type記錄請求的類型。

代碼見

public class MetricFilter implements Filter {
    @Autowired
    private CollectorRegistry collectorRegistry;

    Summary responseTimeInMs;
    Counter totalRequest;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        responseTimeInMs = Summary
            .build()
            .name("http_response_time_milliseconds")
            .labelNames("method", "module", "status", "method_type")
            .help("Request completed time in milliseconds")
            .register(collectorRegistry);

        totalRequest = Counter
            .build()
            .name("http_total_request_size")
            .labelNames("status", "module")
            .help("total http request size")
            .register(collectorRegistry);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String uri = httpRequest.getRequestURI().replace("/", ".");
        Long end = 0L;
        Long start = 0L;
        try {
            start = System.currentTimeMillis();
            chain.doFilter(request, response);
            end = System.currentTimeMillis();
        } finally {
            doMetrics(httpRequest, httpResponse, uri, end, start);
        }

    }

    private void doMetrics(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String uri, Long end,
        Long start) {
        try {
            Pair<String, String> pair = getMethodAndModuleName(uri);
            if (pair != null) {
                int status = switchStatus(httpResponse.getStatus());
                long time = end - start;
                responseTimeInMs.labels(pair.getLeft(), pair.getRight(),
                    String.valueOf(status), httpRequest.getMethod()).observe(time);
                totalRequest.labels(String.valueOf(status), pair.getRight()).inc();
            }
        } catch (Exception e) {
            // ignore exception
        }
    }

    private Pair<String, String> getMethodAndModuleName(String name) {
        if (filterByMethodName(name)) {
            return null;
        }

        Matcher matcher = Pattern.compile("_api_(.*?)_(.*?)_([a-zA-Z0-9_]*)")
            .matcher(name.replaceAll("[^a-zA-Z0-9_]", "_")
                         .replaceAll("_\\d{1,9}", "_id"));
        if (matcher.find()) {
            String module = matcher.group(1) + "_" + matcher.group(2);
            String method = module + "_" + StringUtils.removeEnd(matcher.group(3), "_");
            return new ImmutablePair<>(method, module);
        }

        return null;
    }


    private boolean filterByMethodName(String name) {
        if (name.contains("health") || name.contains("swagger") || name.contains("prometheus")
            || name.contains("metrics") || name.contains("springboot-admin")
            || name.contains("star-star") || name.contains("api-docs")) {
            return true;
        }
        return false;
    }

    @Override
    public void destroy() {
    }


    private Integer switchStatus(Integer status) {
        if (status >= 200 && status < 300) {
            return 200;
        } else if (status >= 400 && status < 500) {
            return 400;
        } else if (status >= 500) {
            return 500;
        }
        return 100;
    }
}

在grafana中展示數(shù)據(jù)

grafana的使用可以到grafana 官網(wǎng)學(xué)習(xí),有時間我們會詳細介紹。

我們僅僅討論怎樣書寫promQL表達式將我們的數(shù)據(jù)按照我們要求完美的展現(xiàn)出來,比如我想統(tǒng)計1分鐘內(nèi)每個服務(wù)的可用性。promQL如下:

(1+sum(http_total_request_size{status="200"}) by (module)-sum(http_total_request_size{status="200"} offset 30s) by (module)) * 100/ (1+sum(http_total_request_size{status=~"200|500"})by (module) -sum(http_total_request_size{status=~"200|500"} offset 30s)by (module) )

每個服務(wù)的吞吐率:

sum(rate(http_response_time_milliseconds_count{kubernetes_namespace="$namespace"}[1m])) by (module)

還有錯誤調(diào)用Top10

topk(10,sum(http_response_time_milliseconds_count{status!="200"}) by(method,method_type,status)  - sum(http_response_time_milliseconds_count{status!="200"} offset 1m) by(method,method_type,status))

等等,在這里就不一一列舉

現(xiàn)在一個實時的監(jiān)控系統(tǒng)已經(jīng)搭建起來,它帶來的優(yōu)勢顯而易見,我們可以實時觀測各個服務(wù)的健康狀況,一目了然。同時它帶來了解決問題方式的改變,以往沒有實時監(jiān)控的時候,我們的服務(wù)某個接口報錯一定需要等到用戶反饋或者調(diào)用方反饋,我們才能發(fā)現(xiàn)并被動的解決問題?,F(xiàn)在我們可以清晰的看到那個接口調(diào)用報錯,在用戶或者調(diào)用方還沒有反饋之前就已經(jīng)發(fā)現(xiàn)了問題并主動的解決掉問題。這帶來解決問題方式的一個革新。

配置將Prometheus 動態(tài)發(fā)現(xiàn) Zuul

因為Zuul 的實例有多個,我們可以通過 Kubernetes 的服務(wù)發(fā)現(xiàn)完成

      - job_name: 'zuul'
        metrics_path: '/prometheus'
        kubernetes_sd_configs:
          - role: pod
        relabel_configs:
          - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
            action: replace
            regex: (.+):(?:\d+);(\d+)
            replacement: ${1}:${2}
            target_label: __address__
          - source_labels: [__meta_kubernetes_pod_name]
            action: keep
            regex: zuul-server-(.+)

也可以自己實現(xiàn)一套file_sd,輸出的格式如下所示

[{"targets":["192.168.77.153:8041","192.168.77.164:8041","192.168.77.156:8041"]}]

配置

file_sd_configs:    #file dynamic discovery
  - files:
    - /config/config.json
      refresh_interval: 30s

修改配置文件以后,reload 一下配置,在 status 菜單下的targets 標(biāo)簽下可以觀察到Prometheus 動態(tài)拉取到了Zuul 的地址

Prometheus查詢性能的優(yōu)化

當(dāng)我們以為一切都萬事大吉的時候,在使用過程中,我們發(fā)現(xiàn)了一個有點嚴(yán)重的問題,就是Grafana 的Dashboard 監(jiān)控面板需要很長一段時間才能將我們需要的監(jiān)測結(jié)果給顯示出來,
這是非常致命的,因為我們這個Prometheus 是一個實時監(jiān)控平臺,如果需要花費3-5 分鐘才將數(shù)據(jù)顯示出來就失去了實時性的意義了,所以對我們來說這是一個必須要解決的問題。下面我們將整個解決問題的過程呈現(xiàn)出來供大家參考。
Prometheus 使用的是本地磁盤存儲,它非常耗內(nèi)存和cpu,生產(chǎn)上的數(shù)據(jù)是相當(dāng)龐大的,當(dāng)我們寫了一個性能非常不好promQL時,Prometheus將進行大量的計算,雖然Prometheus 已經(jīng)針對時序數(shù)據(jù)的計算做了優(yōu)化,但是仍然阻止不了我們寫出性能很差的查詢語句。

第一階段優(yōu)化

我們一開始給指標(biāo)名起名的格式為:指標(biāo)功能模塊名訪問方法路徑,沒有打label,最后數(shù)據(jù)展示的時候我們大量使用{name =~ “.api.get.*“}去正則過濾,最后導(dǎo)致的結(jié)果是查詢非常慢。
最后我們的改進措施是,將模塊名、請求方法、返回狀態(tài)嗎、請求類型都設(shè)置為標(biāo)簽,猜測應(yīng)該是有類似索引的優(yōu)化。

第二階段的優(yōu)化

上面的優(yōu)化措施過后,我們的查詢性能大大提高,在開發(fā)和測試環(huán)境中已經(jīng)非常流暢的能顯示出圖片,但是當(dāng)我們的機器到了生產(chǎn)上面之后,我們發(fā)現(xiàn)生產(chǎn)的數(shù)據(jù)量很巨大,Grafana的顯示速度還算能接受,但是Prometheus那臺機器的cpu負載間歇性突增。
我們發(fā)現(xiàn)可能是我們生產(chǎn)上是抓取的時間間隔縮短,同時抓取的數(shù)據(jù)量非常龐大,每一次發(fā)送請求都會進行大量的計算,導(dǎo)致cpu負載間歇性突增。為了改善性能,我們第一步將大量我們沒有使用到的自定義監(jiān)測數(shù)據(jù)刪掉不予記錄,prometheus抓取的數(shù)據(jù)大大減少;第二步我們將可以在客戶端聚合的數(shù)據(jù)先在客戶端聚合好,然后在存入prometheus,而不是每一次請求都讓prometheus去計算聚合一次,比如統(tǒng)計每個模塊的返回值200和500狀態(tài)碼的調(diào)用總次數(shù)
優(yōu)化后

sum(http_total_request_size{status=~"200|500"})by (module)

優(yōu)化前

sum(http_response_time_milliseconds_count{status="200|500"}) by (module)

真的非常完美。

寫在最后

經(jīng)過以上的搭建過程和開發(fā)過程,我們已經(jīng)非常完美的搭建了一個實時的容器性能監(jiān)控系統(tǒng),能夠很好的完成我們對生產(chǎn)上應(yīng)用的監(jiān)控。在本篇文章中還有很多方面沒有詳述,如Prometheus 的配置、Grafana 的使用、預(yù)警的設(shè)置、Grafana 變量模板的使用……,大家可以自己去研究探索。我們是銀天下DevOps 團隊,如果你有任何疑問和技術(shù)問題,歡迎與我們聯(lián)系!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容