當前位置:首頁 » 網頁前端 » alertmanager執行腳本
擴展閱讀
webinf下怎麼引入js 2023-08-31 21:54:13
堡壘機怎麼打開web 2023-08-31 21:54:11

alertmanager執行腳本

發布時間: 2022-08-17 21:19:43

1. 故障恢復方法 告警

‍測試環境中出現了一個異常的告警現象:一條告警通過 Thanos Ruler 的 HTTP 介面觀察到持續處於 active 狀態,但是從 AlertManager 這邊看這條告警為已解決狀態。按照 DMP 平台的設計,告警已解決指的是告警上設置的結束時間已經過了當前時間。一條發送至 AlertManager 的告警為已解決狀態有三種可能:1. 手動解決了告警2. 告警只產生了一次,第二次計算告警規則時會發送一個已解決的告警3. AlertManager 接收到的告警會帶著一個自動解決時間,如果還沒到達自動解決時間,則將該時間重置為 24h 後首先,因為了解到測試環境沒有手動解決過異常告警,排除第一條;其次,由於該告警持續處於 active 狀態,所以不會是因為告警只產生了一次而接收到已解決狀態的告警,排除第二條;最後,告警的告警的產生時間與自動解決時間相差不是 24h,排除第三條。那問題出在什麼地方呢?

分析

下面我們開始分析這個問題。綜合第一節的描述,初步的猜想是告警在到達 AlertManager 前的某些階段的處理過程太長,導致告警到達 AlertManager 後就已經過了自動解決時間。我們從分析平台里一條告警的流轉過程入手,找出告警在哪個處理階段耗時過長。首先,一條告警的產生需要兩方面的配合:

  • metric 數據

  • 告警規則

  • 將 metric 數據輸入到告警規則進行計算,如果符合條件則產生告警。DMP 平台集成了 Thanos 的相關組件,數據的提供和計算則會分開,數據還是由 Prometheus Server 提供,而告警規則的計算則交由 Thanos Rule(下文簡稱 Ruler)處理。下圖是 Ruler 組件在集群中所處的位置:

  • 首先,圖中每個告警規則 Rule 都有一個 active queue(下面簡稱本地隊列),用來保存一個告警規則下的活躍告警。

    其次,從本地隊列中取出告警,發送至 AlertManager 前,會被放入 Thanos Rule Queue(下面簡稱緩沖隊列),該緩沖隊列有兩個屬性:

    capacity(默認值為 10000):控制緩沖隊列的大小,

    maxBatchSize(默認值為 100):控制單次發送到 AlertManager 的最大告警數

    了解了上述過程,再通過翻閱 Ruler 源碼發現,一條告警在放入緩沖隊列前,會為其設置一個默認的自動解決時間(當前時間 + 3m),這里是影響告警自動解決的開始時間,在這以後,有兩個階段可能影響告警的處理:1.緩沖隊列階段2.出緩沖隊列到 AlertManager 階段(網路延遲影響)由於測試環境是區域網環境,並且也沒在環境上發現網路相關的問題,我們初步排除第二個階段的影響,下面我們將注意力放在緩沖隊列上。通過相關源碼發現,告警在緩沖隊列中的處理過程大致如下:如果本地隊列中存在一條告警,其上次發送之間距離現在超過了 1m(默認值,可修改),則將該告警放入緩沖隊列,並從緩沖隊列中推送最多 maxBatchSize 個告警發送至 AlertManager。反之,如果所有本地隊列中的告警,在最近 1m 內都有發送過,那麼就不會推送緩沖隊列中的告警。也就是說,如果在一段時間內,產生了大量重復的告警,緩沖隊列的推送頻率會下降。隊列的生產方太多,消費方太少,該隊列中的告警就會產生堆積的現象。因此我們不難猜測,問題原因很可能是是緩沖隊列推送頻率變低的情況下,單次推送的告警數量太少,導致緩沖隊列堆積。下面我們通過兩個方面驗證上述猜想:首先通過日誌可以得到隊列在大約 20000s 內推送了大約 2000 次,即平均 10s 推送一次。結合緩沖隊列的具體屬性,一條存在於隊列中的告警大約需要 (capacity/maxBatchSize)*10s = 16m,AlertManager 在接收到告警後早已超過了默認的自動解決時間(3m)。其次,Ruler 提供了 3 個 metric 的值來監控緩沖隊列的運行情況:

    thanos_alert_queue_alerts_dropped_total

    thanos_alert_queue_alerts_pushed_total

    thanos_alert_queue_alerts_popped_total

    通過觀察 thanos_alert_queue_alerts_dropped_total 的值,看到存在告警丟失的總數,也能佐證了緩沖隊列在某些時刻存在已滿的情況。

    解決通過以上的分析,我們基本確定了問題的根源:Ruler 組件內置的緩沖隊列堆積造成了告警發送的延遲。針對這個問題,我們選擇調整隊列的 maxBatchSize 值。下面介紹一下這個值如何設置的思路。由於每計算一次告警規則就會嘗試推送一次緩沖隊列,我們通過估計一個告警數量的最大值,得到 maxBatchSize 可以設置的最小值。假設你的業務系統需要監控的實體數量分別為 x1、x2、x3、...、xn,實體上的告警規則數量分別有 y1、y2、y3、...、yn,那麼一次能產生的告警數量最多是(x1 * y2 + x2 * y2 + x3 * y3 + ... + xn * yn),最多推送(y1 + y2 + y3 + ... + yn)次,所以要使緩沖隊列不堆積,maxBatchSize 應該滿足:maxBatchSize >= (x1 * y2 + x2 * y2 + x3 * y3 + ... + xn * yn) / (y1 + y2 + y3 + ... + yn),假設 x = max(x1,x2, ...,xn), 將不等式右邊適當放大後為 x,即 maxBatchSize 的最小值為 x。也就是說,可以將 maxBatchSize 設置為系統中數量最大的那一類監控實體,對於 DMP 平台,一般來說是 MySQL 實例。

    注意事項

    上面的計算過程只是提供一個參考思路,如果最終計算出該值過大,很有可能對 AlertManager 造成壓力,因而失去緩沖隊列的作用,所以還是需要結合實際情況,具體分析。因為 DMP 將 Ruler 集成到了自己的組件中,所以可以比較方便地對這個值進行修改。如果是依照官方文檔的介紹使用的 Ruler 組件,那麼需要對源碼文件進行定製化修改。

2. react native 怎麼獲取伺服器ksoap協議的數據

React Native用iOS自帶的JavaScriptCore作為JS的解析引擎,但並沒有用到JavaScriptCore提供的一些可以讓JS與OC互調的特性,而是自己實現了一套機制,這套機制可以通用於所有JS引擎上,在沒有JavaScriptCore的情況下也可以用webview代替,實際上項目里就已經有了用webview作為解析引擎的實現,應該是用於兼容iOS7以下沒有JavascriptCore的版本。
普通的JS-OC通信實際上很簡單,OC向JS傳信息有現成的介面,像webview提供的-方法可以直接在當前context上執行一段JS腳本,並且可以獲取執行後的返回值,這個返回值就相當於JS向OC傳遞信息。React Native也是以此為基礎,通過各種手段,實現了在OC定義一個模塊方法,JS可以直接調用這個模塊方法並還可以無縫銜接回調。
舉個例子,OC定義了一個模塊RCTSQLManager,裡面有個方法-query:successCallback:,JS可以直接調用RCTSQLManager.query並通過回調獲取執行結果。:

1
2
3
4
5
6
7
8
9

//OC:
@implement RCTSQLManager
- (void)query:(NSString *)queryData successCallback:(RCTResponseSenderBlOCk)responseSender
{
RCT_EXPORT();
NSString *ret = @"ret"
responseSender(ret);
}
@end

1
2
3
4

//JS:
RCTSQLManager.query("SELECT * FROM table", function(result) {
//result == "ret";
});

接下來看看它是怎樣實現的。
模塊配置表
首先OC要告訴JS它有什麼模塊,模塊里有什麼方法,JS才知道有這些方法後才有可能去調用這些方法。這里的實現是OC生成一份模塊配置表傳給JS,配置表裡包括了所有模塊和模塊里方法的信息。例:

01
02
03
04
05
06
07
08
09
10
11
12
13
14

{
"remoteMoleConfig": {
"RCTSQLManager": {
"methods": {
"query": {
"type": "remote",
"methodID": 0
}
},
"moleID": 4
},
...
},
}

OC端和JS端分別各有一個bridge,兩個bridge都保存了同樣一份模塊配置表,JS調用OC模塊方法時,通過bridge里的配置表把模塊方法轉為模塊ID和方法ID傳給OC,OC通過bridge的模塊配置表找到對應的方法執行之,以上述代碼為例,流程大概是這樣(先不考慮callback):

在了解這個調用流程之前,我們先來看看OC的模塊配置表式怎麼來的。我們在新建一個OC模塊時,JS和OC都不需要為新的模塊手動去某個地方添加一些配置,模塊配置表是自動生成的,只要項目里有一個模塊,就會把這個模塊加到配置表上,那這個模塊配置表是怎樣自動生成的呢?分兩個步驟:
1.取所有模塊類
每個模塊類都實現了RCTBridgeMole介面,可以通過runtime介面objc_getClassList或objc_ClassList取出項目里所有類,然後逐個判斷是否實現了RCTBridgeMole介面,就可以找到所有模塊類,實現在RCTBridgeMoleClassesByMoleID()方法里。
2.取模塊里暴露給JS的方法
一個模塊里可以有很多方法,一些是可以暴露給JS直接調用的,一些是私有的不想暴露給JS,怎樣做到提取這些暴露的方法呢?我能想到的方法是對要暴露的方法名制定一些規則,比如用RCTExport_作為前綴,然後用runtime方法class_getInstanceMethod取出所有方法名字,提取以RCTExport_為前綴的方法,但這樣做惡心的地方是每個方法必須加前綴。React Native用了另一種黑魔法似的方法解決這個問題:編譯屬性__attribute__。
在上述例子中我們看到模塊方法里有句代碼:RCT_EXPORT(),模塊里的方法加上這個宏就可以實現暴露給JS,無需其他規則,那這個宏做了什麼呢?來看看它的定義:

1
2

#define RCT_EXPORT(JS_name) __attribute__((used, section("__DATA,RCTExport" \
))) static const char *__rct_export_entry__[] = { __func__, #JS_name }

這個宏的作用是用編譯屬性__attribute__給二進制文件新建一個section,屬於__DATA數據段,名字為RCTExport,並在這個段里加入當前方法名。編譯器在編譯時會找到__attribute__進行處理,為生成的可執行文件加入相應的內容。效果可以從linkmap看出來:

01
02
03
04
05
06
07
08
09
10
11
12
13
14

# Sections:
# Address Size Segment Section
0x100001670 0x000C0180 __TEXT __text
...
0x10011EFA0 0x00000330 __DATA RCTExport
0x10011F2D0 0x00000010 __DATA __common
0x10011F2E0 0x000003B8 __DATA __bss
...

0x10011EFA0 0x00000010 [ 4] -[RCTStatusBarManager setStyle:animated:].__rct_export_entry__
0x10011EFB0 0x00000010 [ 4] -[RCTStatusBarManager setHidden:withAnimation:].__rct_export_entry__
0x10011EFC0 0x00000010 [ 5] -[RCTSourceCode getScriptText:failureCallback:].__rct_export_entry__
0x10011EFD0 0x00000010 [ 7] -[RCTAlertManager alertWithArgs:callback:].__rct_export_entry__
...

可以看到可執行文件數據段多了個RCTExport段,內容就是各個要暴露給JS的方法。這些內容是可以在運行時獲取到的,在RCTBridge.m的RCTExportedMethodsByMoleID()方法里獲取這些內容,提取每個方法的類名和方法名,就完成了提取模塊里暴露給JS方法的工作。
整體的模塊類/方法提取實現在RCTRemoteMolesConfig()方法里。
調用流程
接下來看看JS調用OC模塊方法的詳細流程,包括callback回調。這時需要細化一下上述的調用流程圖:

看起來有點復雜,不過一步步說明,應該很容易弄清楚整個流程,圖中每個流程都標了序號,從發起調用到執行回調總共有11個步驟,詳細說明下這些步驟:
1.JS端調用某個OC模塊暴露出來的方法。
2.把上一步的調用分解為MoleName,MethodName,arguments,再扔給MessageQueue處理。
在初始化時模塊配置表上的每一個模塊都生成了對應的remoteMole對象,對象里也生成了跟模塊配置表裡一一對應的方法,這些方法里可以拿到自身的模塊名,方法名,並對callback進行一些處理,再移交給MessageQueue。具體實現在BatchedBridgeFactory.js的_createBridgedMole里,整個實現區區24行代碼,感受下JS的魔力吧。
3.在這一步把JS的callback函數緩存在MessageQueue的一個成員變數里,用CallbackID代表callback。在通過保存在MessageQueue的模塊配置表把上一步傳進來的MoleName和MethodName轉為MoleID和MethodID。
4.把上述步驟得到的MoleID,MethodId,CallbackID和其他參數argus傳給OC。至於具體是怎麼傳的,後面再說。
5.OC接收到消息,通過模塊配置表拿到對應的模塊和方法。
實際上模塊配置表已經經過處理了,跟JS一樣,在初始化時OC也對模塊配置表上的每一個模塊生成了對應的實例並緩存起來,模塊上的每一個方法也都生成了對應的RCTMoleMethod對象,這里通過MoleID和MethodID取到對應的Mole實例和RCTMoleMethod實例進行調用。具體實現在_handleRequestNumber:moleID:methodID:params:。
6.RCTMoleMethod對JS傳過來的每一個參數進行處理。
RCTMoleMethod可以拿到OC要調用的目標方法的每個參數類型,處理JS類型到目標類型的轉換,所有JS傳過來的數字都是NSNumber,這里會轉成對應的int/long/double等類型,更重要的是會為block類型參數的生成一個block。
例如-(void)select:(int)index response:(RCTResponseSenderBlock)callback 這個方法,拿到兩個參數的類型為int,block,JS傳過來的兩個參數類型是NSNumber,NSString(CallbackID),這時會把NSNumber轉為int,NSString(CallbackID)轉為一個block,block的內容是把回調的值和CallbackID傳回給JS。
這些參數組裝完畢後,通過NSInvocation動態調用相應的OC模塊方法。
7.OC模塊方法調用完,執行block回調。
8.調用到第6步說明的RCTMoleMethod生成的block。
9.block里帶著CallbackID和block傳過來的參數去調JS里MessageQueue的方法。
10.MessageQueue通過CallbackID找到相應的JS callback方法。
11.調用callback方法,並把OC帶過來的參數一起傳過去,完成回調。
整個流程就是這樣,簡單概括下,差不多就是:JS函數調用轉MoleID/MethodID -> callback轉CallbackID -> OC根據ID拿到方法 -> 處理參數 -> 調用OC方法 -> 回調CallbackID -> JS通過CallbackID拿到callback執行
事件響應
上述第4步留下一個問題,JS是怎樣把數據傳給OC,讓OC去調相應方法的?
答案是通過返回值。JS不會主動傳遞數據給OC,在調OC方法時,會在上述第4步把MoleID,MethodID等數據加到一個隊列里,等OC過來調JS的任意方法時,再把這個隊列返回給OC,此時OC再執行這個隊列里要調用的方法。
一開始不明白,設計成JS無法直接調用OC,需要在OC去調JS時才通過返回值觸發調用,整個程序還能跑得通嗎。後來想想純native開發里的事件響應機制,就有點理解了。native開發里,什麼時候會執行代碼?只在有事件觸發的時候,這個事件可以是啟動事件,觸摸事件,timer事件,系統事件,回調事件。而在React Native里,這些事件發生時OC都會調用JS相應的模塊方法去處理,處理完這些事件後再執行JS想讓OC執行的方法,而沒有事件發生的時候,是不會執行任何代碼的,這跟native開發里事件響應機制是一致的。
說到OC調用JS,再補充一下,實際上模塊配置表除了有上述OC的模塊remoteMoles外,還保存了JS模塊localMoles,OC調JS某些模塊的方法時,也是通過傳遞MoleID和MethodID去調用的,都會走到-enqueueJSCall:args:方法把兩個ID和參數傳給JS的BatchedBridge.,跟JS調OC原理差不多,就不再贅述了。