網(wǎng)站首頁 編程語言 正文
起因
最近有做一個Prometheus metrics代理的一個小項目,暫稱為prom-proxy
,目的是為了解析特定的指標(biāo)(如容器、traefik、istio等指標(biāo)),然后在原始指標(biāo)中加入應(yīng)用ID(當(dāng)然還有其他指標(biāo)操作,暫且不表)。經(jīng)過簡單的本地驗證,就發(fā)布到聯(lián)調(diào)環(huán)境,跑了幾個禮拜一切正常,以為相安無事。但自以為沒事不代表真的沒事。
昨天突然老環(huán)境和新上prom-proxy
的環(huán)境都出現(xiàn)了數(shù)據(jù)丟失的情況,如下圖:
prom-proxy
有一個自服務(wù)指標(biāo)request_total
,經(jīng)觀察發(fā)現(xiàn),該指標(biāo)增長極慢,因而一開始懷疑是發(fā)送端的問題(這是一個誤區(qū),后面會講為何要增加緩存功能)。
進一步排查,發(fā)現(xiàn)上游發(fā)送端(使用的是victoriaMetrics的vmagent組件)出現(xiàn)了如下錯誤,說明是prom-proxy
消費的數(shù)據(jù)跟不上vmagent產(chǎn)生的數(shù)據(jù):
2022-03-24T09:55:49.945Z warn VictoriaMetrics/app/vmagent/remotewrite/client.go:277 couldn't send a block with size 370113 bytes to "1:secret-url": Post "xxxx": context deadline exceeded (Client.Timeout exceeded while awaiting headers); re-sending the block in 16.000 seconds
出現(xiàn)這種問題,首先想到的是增加并發(fā)處理功能。當(dāng)前的并發(fā)處理數(shù)為8(即后臺的goroutine數(shù)目),考慮到線上宿主機的core有30+,因此直接將并發(fā)處理數(shù)拉到30。經(jīng)驗證發(fā)現(xiàn)毫無改善。
另外想到的一種方式是緩存,如使用kafka或使用golang自帶的緩存chan。但使用緩存也有問題,如果下游消費能力一直跟不上,緩存中將會產(chǎn)生大量積壓的數(shù)據(jù),且Prometheus監(jiān)控指標(biāo)具有時效性,積壓過久的數(shù)據(jù),可用性并不高又浪費存儲空間。
下面是使用了緩存chan的例子,s.reqChan
的初始大小設(shè)置為5000,并使用cacheTotal
指標(biāo)觀察緩存的變更。這種方式下,數(shù)據(jù)接收和處理變?yōu)榱水惒?但并不完全異步)。
上面一開始有講到使用request_total
查看上游的請求是個誤區(qū),是因為請求統(tǒng)計和請求處理是同步的,因此如果請求沒有處理完,就無法接收下一個請求,request_total
也就無法增加。
func (s *Server) injectLabels(w http.ResponseWriter, r *http.Request) { data, _ := DecodeWriteRequest(r.Body) s.reqChan <- data cacheTotal.Inc() w.WriteHeader(http.StatusNoContent) } func (s *Server) Start() { go func() { for data := range s.reqChan { cacheTotal.Dec() processor := s.pool.GetWorkRequest() go func() { processor.JobChan <- data res := <-processor.RetChan if 0 != len(res.errStr) { log.Errorf("err msg:%s,err.code:%d", res.errStr, res.statusCode) return } }() } }() }
上線后觀察發(fā)現(xiàn)cacheTotal
的統(tǒng)計增加很快,說明之前就是因為處理能力不足導(dǎo)致request_total
統(tǒng)計慢。
至此似乎陷入了一個死胡同。多goroutine和緩存都是不可取的。
回顧一下,prom-proxy
中處理了cadvisor、kube-state-metrics、istio和traefik的指標(biāo),同時在處理的時候做了自監(jiān)控,統(tǒng)計了各個類型的指標(biāo)。例如:
prom-proxy_metrics_total{kind="container"} 1.0396728e+07 prom-proxy_metrics_total{kind="istio"} 620414 prom-proxy_metrics_total{kind="total"} 2.6840415e+07
在cacheTotal
迅猛增加的同時,發(fā)現(xiàn)request_total
增長極慢(表示已處理的請求),且istio
類型的指標(biāo)處理速率很慢,,而container
類型的指標(biāo)處理速度則非常快。這是一個疑點。
vmagent的一個請求中可能包含上千個指標(biāo),可能會混合各類指標(biāo),如容器指標(biāo)、網(wǎng)關(guān)指標(biāo)、中間件指標(biāo)等等。
通過排查istio
指標(biāo)處理的相關(guān)代碼,發(fā)現(xiàn)有三處可以優(yōu)化:
- 更精確地匹配需要處理的指標(biāo):之前是通過前綴通配符匹配的,經(jīng)過精確匹配之后,相比之前處理的指標(biāo)數(shù)下降了一半。
- 代碼中有重復(fù)寫入指標(biāo)的bug:這一處IO操作耗時極大
- 將寫入指標(biāo)操作放到獨立的goroutine pool中,獨立于標(biāo)簽處理
經(jīng)過上述優(yōu)化,上線后發(fā)現(xiàn)緩存為0,性能達標(biāo)!
一開始在開發(fā)完prom-proxy
之后也做了簡單的benchmark測試,但考慮到是在辦公網(wǎng)驗證的,網(wǎng)速本來就慢,因此注釋掉了寫入指標(biāo)的代碼,初步驗證性能還算可以就結(jié)束了,沒想到埋了一個深坑。
所以所有功能都需要覆蓋驗證,未驗證的功能點都有可能是坑!
總結(jié)
- 服務(wù)中必須增加必要的自監(jiān)控指標(biāo):對于高頻率請求的服務(wù),增加請求緩存機制,即便不能削峰填谷,也可以作為一個監(jiān)控指標(biāo)(通過Prometheus metric暴露的),用于觀察是否有請求積壓;此外由于很多線上環(huán)境并不能直接到宿主機進行操作,像獲取火焰圖之類的方式往往不可行,此時指標(biāo)就可以作為一個參考模型。
- 進行多維度度、全面的benchmark:代碼性能分為計算型和IO型。前者是算法問題,后者則涉及的問題比較多,如網(wǎng)絡(luò)問題、并發(fā)不足的問題、使用了阻塞IO等。在進行benchmark的時候可以將其分開驗證,即注釋掉可能耗時的IO操作,首先驗證計算型的性能,在計算型性能達標(biāo)時啟用IO操作,進一步做全面的benchmark驗證。
后續(xù)
喜聞樂見的后續(xù)來了。。。
由于公司有兩個大的線上集群,暫稱為more集群和less集群,很不幸,性能達標(biāo)的就是less集群的,其指標(biāo)數(shù)據(jù)相比more集群來說非常less,大概是前者的十分之一。上到more集群之后服務(wù)內(nèi)存直接達到50G,多個副本一起吃內(nèi)存,直接將節(jié)點搞掛了。
迫不得已(又是那句話,感覺對了的點往往不對),重新做了pprof壓力測試,發(fā)現(xiàn)內(nèi)存黑洞就是下面這個函數(shù)(來自Prometheus),即便在辦公電腦下進行壓測,其內(nèi)存使用仍然達到好幾百M。該函數(shù)主要是讀取vmagent傳來的請求,首先進行snappy.Decode
解碼,然后unmarshal
到臨時變量wr
中。低流量下完全沒有問題,但高流量下完全無法應(yīng)對:
func DecodeWriteRequest(r io.Reader) (*ReqData, error) { compressed, err := ioutil.ReadAll(r) if err != nil { return nil, err } reqBuf, err := snappy.Decode(nil, compressed) if err != nil { return nil, err } var wr prompb.WriteRequest if err := proto.Unmarshal(reqBuf, &wr); err != nil { return nil, err } return &ReqData{ reqBuf: reqBuf, wr: &wr, }, nil }
解決辦法就是拿出sync.pool
大殺器,下面方式參考了victoriaMetrics的byteutil庫(代碼路徑lib/byteutil
),有興趣的可以去看下,經(jīng)過壓測,相同測試情況下內(nèi)存降到了不足100M。
func DecodeWriteRequest(r io.Reader, callback func(*prompb.WriteRequest)) error { ctx := getPushCtx(r) defer putPushCtx(ctx) if err := ctx.Read(); err != nil { return err } bb := bodyBufferPool.Get() defer bodyBufferPool.Put(bb) var err error bb.B, err = snappy.Decode(bb.B[:cap(bb.B)], ctx.reqBuf.B) if err != nil { return err } wr := getWriteRequest() defer putWriteRequest(wr) if err := wr.Unmarshal(bb.B); err != nil { return err } callback(wr) return nil }
這樣一來性能完全達標(biāo),10core下單pod每秒可以處理250w個指標(biāo)!
重新發(fā)布線上,自然又出問題了,這次prom-proxy
服務(wù)一切正常,但導(dǎo)致后端vmstorage(victoriametrics的存儲服務(wù))內(nèi)存爆滿。經(jīng)過初步定位,是由于出現(xiàn)了slow insert,即出現(xiàn)大量?active time series導(dǎo)致緩存miss,進而導(dǎo)致內(nèi)存暴增(prom-proxy
服務(wù)會在原始指標(biāo)中增加標(biāo)簽,并創(chuàng)建其他新的指標(biāo),這兩類指標(biāo)數(shù)目非常龐大,都屬于active time series
)。
最終的解決方式是將修改的指標(biāo)作分類,并支持配置化啟用,即如果修改的指標(biāo)類型有:A、B、C、D四類。首先上線A,然后上線B,以此類推,讓vmstorage逐步處理active time series
,以此減少對后端存儲的瞬時壓力。
vmstorage有一個參數(shù):--storage.maxDailySeries
,它可以限制active time series
的數(shù)目。但環(huán)境中正常情況下就有大量active time serials
,如果設(shè)置了這個參數(shù),新增的active time serials
極有可能會擠掉老的active time serials
,造成老數(shù)據(jù)丟失。
原文鏈接:https://www.cnblogs.com/charlieroro/p/16054058.html
相關(guān)推薦
- 2023-01-02 正確在Flutter中添加webview實現(xiàn)詳解_Android
- 2022-09-20 關(guān)于go-zero單體服務(wù)使用泛型簡化注冊Handler路由的問題_Golang
- 2023-01-05 Go單例模式與Once源碼實現(xiàn)_Golang
- 2023-01-06 Linux下find?命令的?7?種用法_linux shell
- 2024-01-29 理解并使用 XPath 中的 `normalize-space` 函數(shù)
- 2022-10-23 C#使用Task實現(xiàn)并行編程_C#教程
- 2022-12-12 python中的線程池threadpool_python
- 2022-04-19 Django項目中動態(tài)設(shè)置靜態(tài)文件路徑的全過程_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支