Stack Overflow:我們是如何做監控的

什麼是監控?我認爲,對不同的人,這意味着不同的東西。但是,對於這個概念,我們或多或少有一些一致的看法。當有人說監控時,我會想到:
圖片
但顯然,有些人會想到別的事情。這些人顯然是錯的,讓我們繼續。當我讀了某個白癡寫的一篇10000字的博文後,我就不再那麼遲鈍了。我把監控看作是一個注視你的東西的過程,就像一個保安坐在某處一張堆滿攝像機的桌子前一樣。有時他們會睡着——這是監控系統宕機。有時他們會因爲甜甜圈送到而分心——這是升級停機。有時攝像機在循環播放——看着那個畫面,我不知道去哪裏,但是可能有人在偷你的東西。還有,你有火警警報器。你不需要人來觸發它。同樣的道理,當門被打開的時候,那可能連接到警報器上了。也許沒有。或者,警報器1984年就壞了。

我知道你是怎麼想的。我的觀點是,監控任何應用程序與監控其他任何東西沒有太大區別。有些事情你可以自動化。有些事情你做不到。有些東西可以根據閾值告警。有時你會弄錯這些閾值(尤其是在節假日)。有時,進一步的自動化設置不太值得,你只是需要使人更容易看明白。

這裏我要討論的是我們所做的事情。每個人的情況都不一樣。什麼是重要的,什麼是“值得的”,對每個人來說都不相同。就像生活中的其他事情一樣,需要權衡取捨做出許多決定。以下是我們到目前爲止所做的決定。它們不完美。它們還在發展。當新的數據或優先事項出現時,我們將在必要時改變先前的決定。這就是大腦的工作原理。

數據類型

監控通常涉及多種數據類型,我對其做了如下分類:

  • 日誌:豐富而詳細的文本和數據,但不是讓人害怕的警告;
  • 指標:遙測數據的標記數值——適用於警告,但缺少細節;
  • 健康檢查:運行正常?宕機?異常?非常具體,通常是爲了警告;
  • 性能分析:應用程序性能數據,用來查看事情耗費了多長時間;
  • 其他具體用例的複雜組合,無法歸入任何一類。

日誌

讓我們聊下日誌。你幾乎可以記錄任何東西,包括信息消息、錯誤、流量、電子郵件(小心GDPR)等。

聽起來不錯。我可以記錄任何我想要的東西!有什麼需要注意嗎?這是一種權衡。你是否曾經運行過具有大量控制檯輸出的程序?在沒有輸出的情況下運行相同的程序速度更快,不是嗎?日誌記錄有一些開銷。首先,通常需要爲日誌本身分配字符串。這就涉及內存和垃圾收集(對於.NET和其他一些平臺)。當你在某處記錄日誌時,這通常意味着磁盤空間。如果我們遍歷一個網絡(在某種程度上是局部的),這也意味着帶寬和延遲。

剛剛,我只在電子郵件處提到了GDPR……GDPR是記錄上述所有數據時都要考慮的。記錄的任何東西都要保留並且合規。這是另外一項需要考慮的成本。

假設這些都不是大問題,而我們想要記錄所有的東西。好吧,我們就可以擁有太多的好東西。當你需要查看日誌時會發生什麼?會需要更多地挖掘。這會使發現問題變得更加困難和緩慢。對於所有的日誌記錄,都是在你認爲需要的記錄與最終需要的記錄之間進行權衡。你會弄錯。一直都會。當出現問題時,你會發現新添加的特性沒有正確的日誌記錄。你最終會明白的(可能在遇到問題之後)…並添加相應的日誌。這就是生活。改進並繼續前進。不要老是想着它,只要吸取教訓並從中學習。以後,你將在代碼評審等方面對此進行更多的思考。

那麼我們記錄什麼呢?這取決於系統。對於我們構建的任何系統,我們總是會記錄錯誤。我們使用了StackExchange.Exceptional,這是我維護的一個開源的.NET錯誤日誌程序。它記錄在SQL Server裏,可以在應用程序內或通過Opserver查看(稍後我們將詳細討論)。

對於像RedisElasticsearchSQL Server這樣的系統,我們只是使用它們內置的日誌記錄和日誌輪轉機制將日誌記錄到本地磁盤。對於其他基於SNMP的系統,如網絡設備,我們將所有這些轉發到Logstash集羣,通過前端的Kibana進行查詢。Bosun在出現警告時也會查詢上面的許多內容,以瞭解詳細的信息和趨勢,下面我們將深入地討論這些內容。

日誌:HAProxy

我們也記錄經過HAProxy(我們的負載均衡器)的公共HTTP請求的最小摘要(只是頂級…沒有Cookies、沒有表單數據等),因爲當有人不能登錄,賬戶合併,或任何其他上百個Bug報告進來時,能夠看下是什麼導致了他們的問題會非常有價值。我們是在SQL Server中通過聚簇列存儲索引來實現的。根據記載,Jarrod Dixon在大約8年前第一次建議並開始了HTTP日誌記錄,我們那時都說他是個瘋子,這是對資源的巨大浪費。沒有人說他是對的。一種新的按月存儲格式即將推出,但那是另外一個故事了。

在那些請求中,我們使用了我們很快就會談到的性能分析,並把頭信息和特定的性能數值一起發送到HAProxy。HAProxy捕獲那些頭信息並拆分成系統日誌行,我們會轉發並處理成SQL。那些頭信息包括:

  • ASP.NET總毫秒數(包含下面這些);
  • SQL計數(查詢)&毫秒數;
  • Redis計數(命中)&毫秒數;
  • HTTP計數(發送的請求)&毫秒數;
  • 標籤引擎計數(查詢)&毫秒數;
  • Elasticsearch計數(命中)&毫秒數。

如果事情變好或變壞,我們可以很容易地查詢並與歷史數據進行比較。它還以我們從未想到的方式發揮作用。例如,當我們查看一個請求和運行中的SQL查詢數量,它能告訴我們用戶沿着代碼路徑走了多遠。或者,當SQL連接池請求過多時,我們可以查看在特定時間內來自特定服務器的所有請求,看看是什麼導致了這種爭用。在這裏,我們所做的就是跟蹤n個服務的調用次數和時間。這超級簡單,但也非常有效。

我們將系統日誌監聽以及保存成SQL的過程稱之爲“流量處理服務(Traffic Processing Service)”,因爲我們計劃每天發送報告。

對於每個請求,除了那些頭之外,HAProxy默認的日誌行格式還有一些其他的時間:

  • TR:客戶端發送請求耗時(這在keepalive發揮作用時特別有用);
  • Tw:在隊列中等待耗時;
  • Tc:等待連接到Web服務器耗時;
  • Tr:Web服務器完全渲染一個響應的耗時。

另一個簡單但重要的例子是Tr和AspNetDurationMs頭(一個計時器會在請求開始時啓動和結束時停止)之間的增量,它可以告訴我們在操作系統中耗費的時間,在IIS中等待線程的時間等等。

健康檢查

健康檢查就是檢查健康狀況。“這健康嗎?”,對於這個問題通常有4個答案:

  • :“一切正常!”
  • 不: “@#$%!我們宕了!”
  • 有點:“嗯,我想,從技術上講,我們是在線的……”
  • 不知道:“沒有任何線索……他們沒有接電話。”

按照約定,它們通常分別是綠色、紅色、黃色和灰色。健康檢查有一些一般用法。在任何分佈式負載設置中,如一個服務器集羣或一組服務器前的負載均衡器,健康檢查是一種查看成員是否勝任某個角色或任務的方法。例如,在Elasticsearch中,如果一個節點宕機,它將重新平衡分片並在其他成員中加載……當節點恢復健康時,再次執行此操作。在Web層,負載均衡器將停止向宕機節點發送流量,並繼續在健康節點之間進行平衡。

對於HAProxy,我們使用內置的健康檢查,它可以進行健康警告。到2018年底,當我寫這篇文章的時候,我們還在使用 ASP.NET MVC5,並且正在向 .NET Core轉換。一個重要的細節是,我們的錯誤頁面是一個重定向,例如/questions到/error?aspxerrorpath=/questions。這是.NET舊基礎設施工作機制的一個實現細節,但當與HAProxy結合時,這成了一個問題。例如,如果你有下面這樣一個請求:

server ny-web01 10.x.x.1:80 check

那麼,它將收到一個200-399之間的HTTP狀態碼響應。(還請記住:它只發出HEAD請求。)400或500將觸發不健康,但我們的302重定向不會。瀏覽器*在重定向之後*將獲得一個5xx狀態代碼,但HAProxy不會這樣做。它只做最初的檢查,而一個“健康”的302是它所看到的一切。幸運的是,你可以在同一個後端使用http-check expect 200(或任何狀態代碼,或範圍,或正則表達式——文檔在這裏)來更改它。這意味着我們的健康檢查端點只允許200。是的,它不止一次給我們帶來傷害。

不同的應用程序有不同的健康檢查端點,但對於stackoverflow.com來說是主頁。我們已經討論過幾次要改變這一點,但事實是,主頁檢查了我們可能會檢查不到的東西,而全面檢查很重要。我的意思是,“如果多個用戶點擊同一個頁面,它會發揮作用嗎?”如果我們對數據庫和某些緩存的訪問進行健康檢查,對我們知道需要保持在線的重要內容進行一致性檢查,這很好,這比什麼都不做要好得多。但是,假設我們在代碼中放置了一個Bug,而看似沒有那麼重要的緩存未能正確地重新加載,其結果就是需要爲所有用戶渲染頂部狀態欄。現在,每一頁都遭到了破壞。運行某些代碼的健康檢查路由不會被觸發,但是,加載主視圖的動作可以保證對大量依賴項進行評估,確保它們在進行檢查時可以正常發揮作用。

我們在庫中也有健康檢查。最簡單的形式就是心跳,如用StackExchange.Redis定期檢查到Redis的套接字連接是否活動。我們使用相同的方法來查看套接字是否仍然打開,並且正在被Stack Overflow上的WebSocket消費者所使用。這是一種不常用的監控,但確實用了。

我們還有其他的健康檢查,包括標記引擎服務器。我們可以通過HAProxy(它會添加一個躍點)來平衡負載,但是,讓每個Web層服務器直接知道每個標記服務器對我們來說是一個更好的選項。我們可以1)選擇如何分配負載,2)更容易地測試新構建,3)獲得每個服務器的操作計數指標和性能數據。所有這些都在另一篇文章中介紹,但是對於這個主題:我們有一個簡單的“ping”健康檢查,每秒探測一下標記服務器,從它獲取少量的數據,比如它最近一次從數據庫更新的時間。

所以,就是這樣。你完全可以通過健康檢查來傳達你想要的狀態。如果它能帶來一些好處,而且開銷是值得的(如你正在運行另一個查詢嗎?),那就試試看吧。微軟的.NET團隊一直致力於在ASP.NET Core中提供統一的健康檢查方法,但我不確定我們是否會那樣做。我希望我們能提供一些想法,並在我們開始做的時候進行統一……在這個過程中,我們會有更多的想法。

但是,請記住,健康檢查通常會經常運行,很經常。它們的開銷和可擴展性應該與它們運行的頻率聯繫起來。如果你每100毫秒一次、每秒一次、每5秒一次或每分鐘檢查一次,那麼檢查什麼以及評估多少依賴項(並花一些時間檢查……)就非常重要。例如,100毫秒一次的檢查不能用200毫秒。那太過分了。

這裏有另外一個需要注意的事項,健康檢查通常可以反映幾個級別的“運行(up)”。一個是“我在這裏”,這是最基本的。另一個是“我準備好提供服務了”。後者對於幾乎每個用例都更加重要。但是,對於機器,你不能那樣說,你要用它們喜歡的方式。

在Stack Overflow,有一個這樣的實際例子:在將HAProxy後端服務器從MAINT(維護模式)切換到ENABLE時,我們會假設後端一直處於運行狀態,直到健康檢查顯示情況並非如此。但是,當從DRAIN切換到ENABLE時,我們會假設服務已經關閉,並且必須通過3次健康檢查才能獲得流量。當我們處理線程池增長限制和試圖增加的緩存(如Redis連接)時,我們可能會由於健康檢查的行爲而遇到非常嚴重的線程池飢餓問題。其影響非常大。當我們從耗盡狀態緩慢地增加時,大約需要8-20秒才能完全準備好爲新構建的Web服務器流量提供服務。如果我們從維護狀態開始,在服務器啓動時,流量會涌入服務器,這個過程需要2-3分鐘。健康檢查和流量涌入似乎是很明顯的細節,但它對我們的部署管道至關重要。

健康檢查:httpUnit

我們有一個內部工具(同樣是開源的!)是httpUnit。它是一個相當易於使用的工具,我們使用它來檢查端點的法規遵從性。這個URL是否返回我們期望的狀態代碼?來點文本檢查怎麼樣?證書有效嗎?(如果無效,我們就無法連接。)防火牆允許這個規則嗎?

通過不斷進行這項檢查並在失敗時將其納入警告,我們可以快速地識別問題,特別是從無效配置更改到基礎設施的問題。在應用用戶負載之前,我們還可以隨時測試新的配置或基礎設施、防火牆規則等。要了解更多細節,請參見GitHub README

健康檢查:Fastly

如果我們把視角從數據中心移開,我們需要看看是什麼在訪問我們。這通常是我們的CDN和代理:Fastly。Fastly有一個服務的概念,當你把它看作負載均衡器時,它類似於HAProxy後端。Fastly還內置了健康檢查功能。在我們的每個數據中心中,爲了保證冗餘,我們都有兩組ISP。這裏,我們可以在Fastly中進行配置,優化正常運行時間。

比如,NY數據中心目前是我們的主數據中心,CO是我們的備份數據中心。在這種情況下,我們希望嘗試:

  1. NY主ISP
  2. NY輔ISP
  3. CO主ISP
  4. CO輔ISP

採用主輔ISP的原因與最佳傳輸選項、提交、溢出等有關。記住這一點,我們就可以在它們之間做出優先選擇。通過健康檢查,我們可以非常快速地從#1故障轉移到#4。假設有人在#1中切斷了兩個ISP的光纖或者BGP變得不可靠,那麼#2會立即啓動。我們可能會在它發生之前丟棄數千個請求,但是我們討論的是秒級的事情,用戶僅僅刷新頁面可能就又回到業務中了。這很完美嗎?不是。這是否比無限期地宕機要好?絕對啊。

健康檢查:外部

我們也使用一些外部健康檢查。監控一個全球性服務是很重要的。我們運行正常嗎?Fastly運行正常嗎?我們在這兒運行正常嗎?我們在那裏運行正常嗎?我們在西伯利亞運行正常嗎?誰知道呢!?我們有許多供應商,有大量的節點,進行監控的話需要大量的設置和配置……或者我們可以給一些人支付少幾個數量級的錢來把這項工作外包。我們使用Pingdom來實現這一點。當事情變糟時,它會提醒我們。

指標

指標是什麼?它們可以有幾種形式,但對我們來說,它們是有標記的時間序列數據。簡而言之,這意味着你有一個名稱、一個時間戳、一個值,在我們的情況下,還有一些標記。例如,單條記是下面這個樣子:

  • 名稱:dotnet.memory.gc_collections
  • 時間:2018-01-01 18:30:00(UTC)
  • 值:129,389,139
  • 標記:服務器:NY-WEB01,應用程序:StackExchange-Network

記錄中的值也有幾種形式可供採用,但一般情況下是計數器。計數器報告一個不斷增長的值(通常在重啓時重置爲0)。通過計數值隨時間的變化,我們可以求出時間窗口中值的增量。例如,如果10分鐘之前的值是129,389,039,那麼我們就知道,服務器上的進程在這10分鐘內運行了100次0代垃圾收集。另一個情況是報告一個準確時間點的值,例如“這個GPU目前87°”。那麼我們用什麼來處理指標呢?稍後我們會講到Bosun

警告

好了,我們怎麼處理那些數據呢?警告!衆所周知,“alert”是由“le rat”演變而來的一種變位詞,意思是“向當局發出尖叫的人”。

這在幾個層次上發生,我們會爲遇到問題的團隊進行定製,以便它們能以最佳的狀態運行。對於SRE(站點可靠性工程)團隊,Bosun是我們內部的主要警告源。要詳細瞭解Bosun的警告機制,我建議你觀看Kyle在LISA的介紹(大約從第15分鐘開始)。一般來說,我們在會以下情況下會發出警告:

  • 什麼東西宕掉了或者直接發出警告(如iDRAC日誌)
  • 趨勢與先前趨勢不一致(例如x時間比平常少——這往往是假期期間的錯誤警告)
  • 什麼東西遇到了瓶頸(如磁盤空間或網絡超負載)
  • 什麼東西超過了閾值(如某個地方創建的隊列)

還有很多其他的小事情,這些是我一下想到的大類別。

如果有什麼問題已經夠糟糕了,我們就進入下一個階段:喚醒某人。那是在事情變成真正的問題的時候。它們會直接進入PagerDuty並喚醒值班的SRE。如果那個SRE不應答,它將很快升級到另一個SRE。這種級別的事情有:

  • stackoverflow.com(或者其他任何重要的功能)進入離線狀態(Pingdom會看到)
  • 很高的錯誤率

既然我們已經介紹了所有這些煩人的問題,讓我們深入地研究下工具。

Bosun

Bosun是我們用於指標和元數據的內部數據收集工具。它是開源的。沒有什麼現成的東西能真正滿足我們對指標和警告的需求,所以我們在大約四年前創建了Bosun,它給我們提供了極大的幫助。我們可以隨時添加我們想要的任何指標、新功能等等。它具有內部系統的所有優點,也有所有的成本。我待會再談。它是用Go編寫的,這主要是因爲絕大多數指標集都是基於代理的。代理scollector(主要基於tcollector的原則)需要在所有平臺上運行,爲此,Go成爲我們的首選。“嘿,尼克,.NET Core怎麼樣?”也許可以,但還不夠。不過,這個故事越來越有吸引力了。現在,我們可以很容易地部署單個可執行文件,而Go還是領先的。

Bosun後臺使用OpenTSDB進行存儲。它是一個構建在HBase之上的時間序列數據庫,具有很高的可伸縮性。至少人們是這麼告訴我們的。在Stack Exchange/Stack Overflow,我們遇到的問題通常來自效率和吞吐量方面。我們使用少量硬件做了很多事情。在某些方面,這令人印象深刻,我們爲此感到自豪。另一方面,對於那些設計運行方式不同的東西,它會產生扭曲和破壞。對於OpenTSDB的情況,從空間的角度來看,我們不需要很多硬件來運行它,但是HBase的設計方式使得我們必須給它配置更多的硬件(特別是在網絡前端)。當處理的數據量比較少時,有一個HBase複製問題,我在這裏不想深入討論這個問題,因爲這本身就是一篇文章了,而且是一篇很長的文章。

這是一件麻煩事,花費了我們大量的金錢,以至於我們試圖使用SQL Server聚簇列存儲索引作爲Bosun的後臺。我們已經實現了這一點,但對於某些基數的查詢並沒有產生非常顯著的效果,並且會導致CPU使用率很高。Nexus交換核心指標數據佔用的總帶寬比大多數其他指標加起來還多出400倍,像這樣的事情並不可怕。大多數東西運行良好。在一臺像樣的服務器上,每秒記錄50-100k的指標數據只需要大約5%的CPU——這不是問題。某些查詢是痛點,我們還沒有回到那個問題上……這是一個“可能”的問題,我們是否能解決它,需要多少時間。不管怎樣,這也是另一篇文章。

關於Bosun的安裝和配置,如果你想了解更多信息,則可以閱讀Kyle Brandt這篇很棒的介紹其架構的文章

Bosun:指標

對於使用.NET的情況,我們使用BosunReporter發送指標,這是我們維護的另一個開源NuGet庫。它看起來是下面這個樣子:

// 在全局範圍內設置一次
var collector = new MetricsCollector(new BosunOptions(ex => HandleException(ex))
{
	MetricsNamePrefix = "MyApp",
	BosunUrl = "https://bosun.mydomain.com",
	PropertyToTagName = NameTransformers.CamelToLowerSnakeCase,
	DefaultTags = new Dictionary<string, string>
		{ {"host", NameTransformers.Sanitize(Environment.MachineName.ToLower())} }
});

// 當你需要一個指標的時候,就創建一個!在某個地方,這可能應該是靜態的。
// 參數:指標名稱、單位名稱、描述
private static searchCounter = collector.CreateMetric<Counter>("web.search.count", "searches", "Searches against /search");

// 當事件發生時,計數器增加
searchCounter.Increment();

差不多就是這樣。我們現在有了一個流入Bosun的數據計數器。我們可以添加更多的標記——例如,我們把它正在哪個服務器上發生(通過主機標記)包含進去,此外,我們還可以在IIS中添加應用程序池,或者供用戶訪問的問答站點等等。更多細節請查閱BosunReporter README。很好玩。

許多其他的系統也可以發送指標,scollector爲Redis、Windows、Linux等提供了大量的內置收集器。我們用於關鍵監控的另一個外部實例是一個小型Go服務,它可以監聽Fastly實時日誌流。有時候,Fastly可能會返回503,因爲它無法到達我們,或者因爲其他什麼原因,誰知道呢?我們和它們之間的任何東西都可能出錯。可能是被切斷的套接字,或者是路由問題,或者是無效的證書。無論原因是什麼,我們都希望在這些請求失敗且用戶感覺到的時候發出警告。這個小服務只監聽日誌流,從每個條目中解析出一些信息,然後將聚合指標發送給Bosun。目前,這還不是開源的,因爲我不確定我們是否提到過它的存在。如果需要這樣的東西,告訴我們,我們會考慮一下。

Bosun:警告

我非常喜歡Bosun的一個關鍵特性,它能夠在設計時利用歷史數據測試警告。這有助於瞭解它將在何時被觸發。這是一項很棒的完整性檢查。老實說,監控並不完美,它從來都不完美,也永遠不會完美。很多監控都是從經驗教訓中學來的,因爲出錯的事情通常包括你從未想過會出錯的事情……這意味着你沒有從第一天起就監控和/或警告。警告通常是在出錯後添加的。儘管你非常用心,進行了周密的計劃,你還是會漏掉一些事情,並且在第一次事件之後添加警告。沒關係。那是過去的事了。你現在所能做的就是把事情做得更好,希望這種情況不會再發生。不管你是提前設計還是事後回想,這個功能都很棒:
圖片
圖片
你可以看到,11月18日有一個系統慢到足以觸發警告,但其他的都是綠色的。在任何人得到通知之前,通過完整性檢查確認警告是否是噪聲?我喜歡這個特性。

然後我們有嚴重的錯誤,這些錯誤非常緊急,需要儘快解決。對於這些情況,我們會將它們發佈到我們的內部聊天室。像創建Stack Overflow團隊出現錯誤,或者計劃任務失敗了,就屬於這樣的情況。我們還有指標通過以下幾種方式監控(通過Bosun)錯誤:

  • 從我們的異常錯誤日誌(按應用程序彙總)
  • 從Fastly和HAProxy

如果我們在上述兩種情況中的任意一種情況下發現了高錯誤率,那麼一兩分鐘後,帶有詳細信息的消息就會出現在聊天中。(由於它們是基於聚合計數的,因此不能立即警告。)
圖片
這些帶有鏈接的消息讓我們可以快速深入地研究這個問題。網絡出現了問題嗎?我們和Fastly(我們的代理和CDN)之間是否存在路由問題?是不是有一些糟糕的代碼出了問題?有人踢到電源線了嗎?是不是有人把兩個入電器都插到同一個出現故障的UPS上了?所有這些都非常重要,我們希望儘快地進行深入研究。

另一種傳遞警告的方式是電子郵件。Bosun有一些很好的功能可以幫助我們。電子郵件可能只是一個簡單的警告。比方說,磁盤空間正在減少,或者CPU處於高位,而電子郵件中的一個簡單圖表就能說明很多問題……然後我們會有更復雜的警告。比如說,我們更改了共享錯誤存儲中允許的錯誤閾值。很好,我們收到警告了!但是…是哪款應用呢?這是一次性的峯值嗎?正在發生嗎?這時,定義從SQL或Elasticsearch中查詢更多數據的查詢的能力就非常有用了(還記得所有那些日誌記錄嗎?)我們可以在電子郵件中添加故障和詳細信息。你可以更好地處理(甚至決定忽略)電子郵件警告,而無需進一步研究。下面是幾天前NY-TSDB03的CPU突發事件的郵件示例:
圖片
圖片
圖片
我們也包括了在這個有問題的系統上發生的與這個警告相關的最近10個事件,所以你可以很容易地識別一個模式,看看它們爲什麼被忽略,等等。它們沒有出現在這封被我用作示例的電子郵件中。

Grafana

好的,警告很好,但我想看一些數據。畢竟,如果你看不到這些數據,那它們又有什麼用呢?展示和可訪問性很重要。能夠快速地消費數據是很重要的。時間序列數據的圖形可視化是一種很好的探索方法。當涉及到監控時,你必須(1)查看數據,或者(2)擁有非常可靠的100%的警告覆蓋率,這樣就沒有人需要查看數據。第二條是不可能的。當發現問題時,通常需要反過來查看問題是何時開始的。“我們怎麼兩個星期了都沒有注意到這一點?!”這種情況並不像你想象的那麼罕見。所以,歷史視圖是有幫助的。

我們在這裏使用了Grafana。這是一款優秀的開源工具,我們爲它提供了一個Bosun插件,這樣,它就可以作爲一個數據源。(從技術上講,你可以直接使用OpenTSDB,但這個可以增強功能。)關於Crafana的使用,我將依次用一些圖片進行說明。

下面一個狀態儀表板,顯示Fastly的運行情況。因爲我們藉助它們實現DDoS保護和更快的內容交付,所以它們的當前狀態在很大程度上也是我們的當前狀態。
圖片
這只是任意一個我覺得很酷的儀表板。這是按來源國家劃分的流量。它被分成了幾個主要的大洲,你可以看到,當人們醒着的時候,流量是如何在世界各地周而復始地波動的。
圖片
如果你在Twitter上關注我了,你可能會注意到.NET Core存在一些垃圾收集問題。不過,需要對這一點進行關注並不是一個新需求。下面這個儀表板我們已經使用很多年了:
圖片
注意:不要用上面的數字來做任何規模的衡量,這些截圖是在假日週末拍攝的。

客戶端計時

請注意,上面提到的所有內容都是服務器端的。你到現在還在考慮上面的內容嗎?如果你是這樣做的,那就太棒了。很多人直到這成爲問題時纔會進行這樣的思考。但這一直都很重要。

重要的是要記住,你渲染網頁的速度並不重要。唯一重要的是用戶認爲你的網站有多快。感覺有多快?這體現在客戶體驗的許多方面,從最初的頁面繪製到內容閃爍(請不要閃爍或移動!)、廣告呈現等等。

這裏要考慮的因素有,例如,以下動作花了多長時間:

  • 通過TCP連接?(HTTP/3還沒到來)
  • TLS連接協商?
  • 完成請求發送?
  • 獲得第一個字節?
  • 獲得最後一個字節?
  • 頁面的初步繪製?
  • 發出頁面資源請求?
  • 渲染所有東西?
  • 附加JavaScript處理程序?

這些對用戶體驗很重要。我們的問題頁渲染在18毫秒以內。我覺得這太棒了。我甚至可能有偏見。但是,對於用戶來說,如果要花很長時間才能打開,那麼這就是廢話。

那麼,我們能做些什麼呢?幾年前,我根據我們的需要倉促構建了一個可以在瀏覽器中使用的客戶端計時管道。其理念很簡單:使用Web瀏覽器提供的導航計時API並記錄時間。就是這樣。還有一些完整性檢查(你不會相信NTP時鐘校正的數值,在渲染期間,它會從同步中產生無效的計時,導致時鐘倒退)。對於Stack Overflow(或我們網絡中的任何問答站點)5%的請求,我們會請求瀏覽器發送這些計時。我們可以隨意調整這個百分比。

要了解其工作原理,請訪問teststackoverflow.com。下面是一個示例:
圖片
圖片
準確地講,這並不是監控,但有點像。我們使用它來測試一些事情,比如,當我們切換到HTTPS時,連接時間在世界範圍內對每個人的影響(這就是我最初創建計時管道的原因)。當我們添加DNS提供商時也使用了它,在經歷過2016年的Dyn DNS攻擊之後,我們現在已經有了多個提供商。

現在,我們有了一些數據。如果我們從5%的流量中獲得了這些數據,將其發送到服務器,放入SQL Server中一個龐大的聚簇列存儲中,並在此過程中把部分指標發送到Bosun,那麼我們就得到了一些有用的東西。我們可以在配置前後進行測試,查看數據。我們也可以關注當前的流量狀況並查找問題。在最後一部分,我們使用了Grafana,如下圖所示:
圖片
注意:這是一個95百分位視圖,總渲染時間的中值是靠近底部的白點(大多數時間小於500毫秒)。

MiniProfiler

有時候,你希望捕獲的數據比上面的場景更具體也更詳細。在我們的情況下,我們在大約十年前就決定,我們想知道網頁渲染每一個頁面視圖需要多長時間。對於任何東西,監控和查看同樣重要。一個比較好的實現方法是,使它在你查看的每一個頁面視圖上都可見。於是,MiniProfiler誕生了。它有幾種版本(項目略有不同):.NETRubyGoNode.js。我們在這裏將看下我維護的.NET版本:
圖片
在默認情況下,這是你能看到的所有數值,但是你可以展開它,以樹的形式查看什麼事情花費了多長時間。你可以點擊到那裏的連接,快速查看正在運行的SQL或Elastic查詢,或發起的HTTP調用,或獲取的Redis鍵等等,如下圖所示:
圖片
注意:如果你正在想,這比我們所說的問題平均渲染耗時(甚至是第99百分點)要長得多。是的,這是因爲我在這裏多加載了很多東西。

由於MiniProfiler的開銷非常小,所以我們可以在每個請求上運行它。爲此,我們在Redis中保留了每個MVC路由的性能分析樣本。例如,對於任何路由,我們會保留給定時間內最慢的100條性能分析樣本。這讓我們能夠看到哪些用戶可能會點擊我們沒有點擊的內容。或者,可能有匿名用戶使用了不同的查詢,而這個查詢很慢……我們需要能夠看到這些。我們可以看到Bosun中變慢的路由,HAProxy日誌中的點擊率,以及需要深入研究的性能分析快照。所有這些都不需要查看任何代碼,這是一個強大的概覽組合。MiniProfiler非常棒,但在這裏,它也是一個更大的工具集的一部分。

以下是快照和聚合彙總示例:
圖片
圖片
我們或許應該在庫中加入一個這樣的例子。我會盡快抽出時間來做這件事的。

MiniProfiler由Marc GravellSam SaffronJarrod Dixon創建。從版本4.x開始,我成了主要的維護者,但這些先生會對它的存在負責。我們在所有的應用程序中都使用了MiniProfiler。

注意:看到截圖中的那些GUID了嗎?這是MiniProfiler生成的一個ID,我們現在用它作爲“請求ID”,我們會把它記錄到HAProxy日誌中,對於任何異常,亦是如此。像這樣的小東西有助於把世界聯繫在一起,讓你更容易把事情聯繫起來。

Opserver

那麼,Opserver是什麼?這是一個基於Web的儀表板和監控工具,這是我在SQL Server的內置監控欺騙了我們之後開始創建的。大約5年前,我們遇到了一個問題,SQL Server AlwaysOn可用性組在SSMS儀表板上顯示爲綠色(基於主服務器),但是副本已經好幾天沒有新數據了。這是一個監控出現嚴重問題的例子。當時的情況是HADR線程池耗盡,並停止更新處於“一切正常(all good)”狀態的視圖。這樣的設計不一定是缺陷,但是當緩存/存儲一個事物的狀態時,需要有一個時間戳。如果它在時間段內沒有更新,那就是一個紅色警告。狀態的一切都不可信。無論如何,進入Opserver。它所做的第一件事就是監視每個SQL節點,而不是信任主節點。

從那時起,我就針對其他我們想在一個基於Web的快速視圖中查看的系統添加了監控。我們可以看到所有服務器(基於Bosun、Orion或直接基於WMI)。以下是Opserver現狀的概要介紹。

Opserver:主儀表板

首頁儀表板是一個服務器列表,顯示了什麼在運行。用戶可以根據名稱、服務標記、IP地址、VM主機等進行搜索。你還可以下鑽,查看每個節點上CPU、內存和網絡的全時歷史圖。
圖片
節點如下所示:
圖片
如果使用Bosun,並運行Dell服務器,那麼我們會像下面這樣添加硬件元數據:
圖片

Opserver:SQL Server

在SQL Server儀表板中,我們可以看到所有服務器的狀態以及可用性組的運行情況。在任何給定的時間,我們可以看到每個節點有多少活動以及哪個是主節點(藍色部分)。下面的部分是AlwaysOn可用性組,我們可以看到每個組的主節點是哪臺服務器,副本滯後多少,以及備份了多少隊列。如果事情變糟,一個副本不健康,就會出現更多的指示器,比如,顯示哪些數據庫有問題,主服務器上所有與T-logs相關的驅動器的空閒磁盤空間(因爲如果副本繼續宕機,它們將開始增長):
圖片
還有一個頂級的“所有作業(all-jobs)”視圖,可以用於快速監控以及啓用/停用:
圖片
在單實例視圖中,我們可以看到該服務器的統計信息、緩存等,這些是我們隨着時間推移發現的比較重要的指標。
圖片
對於每個實例,我們還會報告頂級查詢(基於計劃緩存,而不是查詢存儲)、當前活動的查詢(基於sp_whoisactive)、連接和數據庫信息。
圖片
圖片
圖片
如果你想下鑽查看一個頂級查詢的詳細信息,則可以看到類似下圖所示的內容:
圖片
在數據庫視圖中,可以下鑽查看錶、索引、視圖、存儲過程和存儲使用情況等信息。
圖片
圖片
圖片
圖片
圖片

Opserver:Redis

對於Redis,我們希望看到主節點和副本節點組成的拓撲鏈以及每個實例的總體狀態。
圖片
圖片
請注意,你可以終止客戶端連接、獲取活動配置、更改服務器拓撲並分析每個數據庫中的數據(通過Regexes進行配置)。最後一個是重量級的KEYSDEBUG OBJECT掃描,因此,我們在副本節點上運行它,或者可以強制在主節點上運行它(爲了安全起見)。下面是一個分析結果示例:
圖片

Opserver:Elasticsearch

對於Elasticsearch,我們通常希望在一個集羣視圖中進行查看,因爲那是它的行爲方式。下圖中沒有索引變爲黃色或紅色。當這種情況發生時,儀表板會新增一部分顯示有問題的分片、它們正在做什麼(初始化、重定位等),以及在每個集羣中出現的次數,彙總顯示有多少分片處於哪種狀態。
圖片
圖片
注意:上面的PagerDuty選項卡從PagerDuty API中提取數據並顯值班信息,以誰爲主,以誰爲輔,你可以查看和聲明事件,等等。(這部分數據不便於分享,所以這裏沒有提供截圖)它還有一個可配置的原始HTML部分,向訪問者提供關於做什麼或聯繫誰的說明。

Opserver:異常

Opserver中的異常是基於StackExchange.Exceptional的。在這一部分,我們將特別關注SQL Server存儲提供程序。對於許多應用程序來說,Opserver是一種共享單個數據庫和表佈局並讓開發人員在一個地方查看其異常的方法。
圖片
這裏的頂層視圖可以是應用程序(默認),也可以在組中配置。在上面的例子中,我們按團隊配置應用程序組,這樣團隊就可以標記或快速點擊他們負責的異常。在每個異常的頁面中,可以看到如下詳細信息:
圖片
圖片
其中還記錄了其他一些細節,比如請求頭(有安全過濾器,因此我們不記錄身份驗證Cookie)、查詢參數和添加到異常中的任何其他自定義數據。

注意:你可以配置多個存儲,例如,我們上面就配置了New York和Colorado。這些是獨立的數據庫,允許所有應用程序登錄到一個非常靠近本地的存儲,並且仍然可以從一個儀表板訪問它們。

Opserver:HAProxy

HAProxy部分非常簡單—我們只是顯示HAProxy的當前狀態並允許對其進行控制。主儀表板如下所示:
圖片
對於每個後臺組、特定的後端服務器、整個服務器或整個層,它也允許一些控制。我們可以把一個後端服務器踢出輪轉,或者讓整個後端停止運行,或者,如果需要的話,我們還可以把一個Web服務器從所有後端中剔除,從而關閉它進行緊急維護,等等。
圖片

Opserver:常見問題

關於Opserver,人們經常問我同樣的問題,以下是對其中一部分問題的回答:

  • Opserver本身不需要任何類型的數據存儲(它是全配置的內存中狀態) 。
    • 將來進行功能增強的時候可能會發生,但現在還沒有添加任何東西的計劃。
  • 只有儀表板選項卡和單節點視圖是基於Bosun、Orion或WMI——SQL、Elastic、Redis等其他界面則沒有任何依賴,Opserver直接對它們進行監控。
  • 身份驗證既可以是全局的,也可以按選項卡進行(查看和管理是分開的),但是,內置的配置是通過組,也包含了活動目錄。
    • 關於管理員和查看者:查看者視圖是隻讀的。例如,HAProxy控制就不會顯示。
  • 所有的選項卡都不是必須的——每一個都是獨立的,只有配置了纔會顯示。
    • 例如,如果你希望只使用Opserver作爲Elastic或異常儀表板,那麼你那樣做就好了。

注意:Opserver目前正在被移植到ASP.NET Core,因爲我晚上有時間。這將使它可以在沒有IIS的情況下運行,並有望很快在其他平臺上運行。有些東西,比如從Linux上進行SQL服務器的AD 身份驗證,仍然有待解決。如果你希望部署Opserver,請注意,部署和配置將發生巨大的變化(會更簡單),所以你最好等一段時間。

接下來的打算

監控是一個不斷髮展的東西。我想,對每個人來說都是這樣。但是,我只能介紹下我參與的計劃,那麼,我們下一步有什麼打算呢?

健康檢查的下一步工作

對於健康檢查的改進,我已經考慮了一段時間了,但還沒能抽出時間。你在監控什麼東西時,真相的來源是一個值得關注的點。有兩個問題,一個是這個東西是什麼,另一個是這個東西應該是什麼。後者由誰定義?我認爲,我們可以在依賴方面做一些改進,讓它的適應面更廣(我真的希望有人已經在做類似的事情,如果是這樣,請告訴我!)如果我們從健康檢查中得出一個類似下面這樣的簡單結構會怎樣?

public class HealthResult
{
    public string AppName { get; set; }
    public string ServerName { get; set; }
    public HealthStatus Status { get; set; }
    public List<string> Tags { get; set; }
    public List<HealthResult> Dependencies { get; set; }
    public Dictionary<string, string> Attributes { get; set; }
}
public enum HealthStatus
{
    Healthy,
    Warning,
    Critical,
    Unknown
}

這只是我的想法,但關鍵是Dependencies。如果你問Web服務器,“嘿,夥計,最近怎麼樣?”它返回的不是一個簡單的JSON對象,而是由它們構成的樹?但是,每一層都是一樣的,所以總的來說,我們有一個遞歸的依賴項列表。例如,一個包含Redis的依賴項列表——如果我們無法到達2個Redis節點中的1個,我們在列表中將有2個依賴項,一個的狀態是Healthy,另一個的是Critical或Unknown,而Web服務器的狀態是Warning而不是Healthy。

這裏的要點是:監控系統不需要知道依賴關係。系統本身定義並返回它們。這樣,我們就不會陷入配置偏離,即被監控的東西與應該存在的東西不匹配。這通常發生在拓撲或依賴關係有變化的部署中。

這可能是一個糟糕的想法,但對於Opserver(或任何腳本)來說,要獲得健康讀數以及健康讀數的原因,這是一個很一般的想法。如果我們失去了另一個節點,就會有n個東西被破壞。或者,我們看到n個健康警告的共同原因。通過指向一些端點,我們可以得到所有事物的樹形視圖。你需要爲自己的用例添加更多的數據嗎?當然!它是JSON,因此,只需要從對象繼承並根據需要添加更多內容即可。這是一個很容易擴展的模型。我需要花點時間來做這個,也許它會有很多問題。或者讀到這篇文章的人會告訴我,這已經完成了。

Bosun的下一步工作

由於沒有資源和其他需要優先處理的事項,Bosun在很大程度上處於維護狀態。我們沒有做我們想的那麼多,因爲我們需要就最佳的前進道路進行討論。是否有其他工具彌補了最初導致我們構建它的缺陷?SQL 2017或2019是否已經大大改善了我們遇到問題的查詢?我們需要花些時間對生態圈做些考察,評估一下我們想做的東西。這是我們想在2019年第一季度開展的工作。

我們知道自己想要做一些事情,例如改進警告編輯體驗以及UI的其他一些方面。我們只是需要權衡一些事情,弄清楚我們的時間用在什麼地方最好。

指標的下一步工作

我們的應用程序對指標的利用還相當地不充分。我們知道這一點。這個系統主要是爲SRE和開發人員構建的,但是,在向開發人員展示指標的所有優點和強大之處(包括指標多麼容易添加)方面,我們做得不夠好。我們在上個月的公司聚會上討論過這個話題。添加指標的代價是如此之低,我們可以做得更好。對此,有各種不同的觀點,但我認爲,這主要是一個我們需要努力改善的培訓和認識問題。

上面的健康檢查……也許我們也可以很容易地集成來自BosunReporter的指標(可能只有在需要時),從而創建一個功能強大的API來檢查服務的健康狀況和狀態。這將使得我們可以對我們通常推送的指標採用拉式模型。不過,它的代價要儘可能小,配置也要少。

工具彙總

我上面提供了多個我們構建和開源的工具。下面是一個供參考的列表。

所有這些工具都有來自我們的開發人員和SRE團隊以及整個社區的額外貢獻。

接下來呢?本系列文章是爲了介紹社區最想了解的內容。從Trello Board來看,緩存似乎是下一個最有趣的主題。因此,下次我們將學習如何在Web層和Redis上緩存數據,如何處理緩存失效,以及如何利用發佈/訂閱來完成各種任務。

查看英文原文:Stack Overflow: How We Do Monitoring - 2018 Edition

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章