報警系統QuickAlarm之報警規則解析

一灰灰發表於2018-03-04

前面兩篇分別說了報警執行器和報警規則的定義及使用者擴充套件載入,接下來就是比較核心的一塊了,如何將報警規則和報警執行器關聯起來,即當發生報警時,應該call哪一個報警執行器

I. 背景知識點

0. 宣告

在正式進入之前,有必要額外宣告一下,因為目前的v1版本,沒有開放報警規則的自定義,也就是說,目前只支援預設的報警規則,所以接下來的主要內容將集中在

  • 系統預設的報警規則的解析
  • 即基於報警頻率閥值,自動選擇報警執行器的規則解析

1. 報警規則

如果對於報警規則,依然不是很清晰的,可以閱讀一下《報警系統QuickAlarm之報警規則的設定與載入》

這裡簡單的進行說明,系統中預設的報警規則結構為:

  • key為報警型別(即使用者執行報警時,傳進來的報警型別引數)
  • value為具體報警規則
    • 每個報警執行器擁有一個報警頻率區間,通過報警頻率對映到報警執行器的區間來選擇對應的AlarmExecutor,這就是系統定義的報警規則

II. 報警規則解析

通過前面的報警規則的簡單說明,基本上也可以撈出報警規則的解析原則了

  • 每種報警型別,對應一個報警規則
  • 每個報警規則中,可以有多個報警執行器
  • 每個報警執行器都有一個對應的報警頻率的閥值
  • 根據閥值對所有的報警執行器排序
  • 計算報警頻率,對映到哪個區間,則選擇哪個報警執行器

上面是一個簡單的解析規則,當然實際上和這個差不多,但有一些問題需要額外注意

  1. 只想選擇一種報警方式,是否可以支援?
  2. 多重報警方式同時呼叫怎麼處理?(如我希望用簡訊提示說有問題,同時用郵件包含詳細的異常堆疊)
  3. 頻率限制
  4. 報警型別沒有設定報警規則如何處理?
  5. 報警規則中使用了一個未註冊的報警執行器會怎樣?

1. 實現方案說明

再次將報警規則類拿出來看一下

/**
 * 報警使用者
 */
private List<String> users;


/**
 * 報警的閥值
 */
private List<AlarmThreshold> alarmThreshold;


/**
 * 最小的報警數
 */
private int minLimit;


/**
 * 最大的報警數
 */
private int maxLimit;


/**
 * 報警型別 {@link IExecute#getName()}
 */
private String alarmLevel;


/**
 * true 表示當報警超過當前的閥值之後, 將提升報警的程度
 */
private boolean autoIncEmergency;
複製程式碼

針對上面的問題,逐一說明

  • 首先是 autoIncEmergency 這個引數,如果為true,則表示可以走上面的哪個區間對映的規則;否則就全部走AlarmConfig中預設的報警型別了
  • minLimit : 表示發生報警的頻率下限值,小於這個值就不會執行具體的報警邏輯
  • maxLimit : 最大的報警頻率,超過了也不報警(簡單的頻率控制)
  • alarmLevel: 對應的就是具體的報警型別
  • alarmThreshold: 這個只有在autoIncEmergency=true時,才有小,也就是我們前面說的不同的報警執行器,根據閥值區間進行排序,開啟之後,遍歷,判斷頻率是否在這個區間內,若在,則表示可以選擇它了
  • 如果不存在報警規則,則採用預設的兜底規則
  • 若報警執行器也不存在,就直接採用系統定義的日誌報警執行器

2. 實現

基本上前面已經將整個邏輯都說了,所以實際的編碼反而比較清晰了

/**
 * 獲取具體的報警執行器
 * <p>
 * 1. 未開啟嚴重等級上升時, 直接返回
 * 2. 開啟之後, 判斷當前的計數 範圍
 *
 * @param alarmConfig 報警配置項, 內部所有的引數都不可能為null
 */
public static ExecuteHelper getExecute(final AlarmConfig alarmConfig, 
  int count) {
    // 未達到報警的下限 or 超過報警的上限時
    if (count < alarmConfig.getMinLimit() || count > alarmConfig.getMaxLimit()) {
        return new ExecuteHelper(SimpleExecuteFactory.getExecute(NoneExecute.NAME), 
        alarmConfig.getUsers());
    }

    // 未開啟報警升級, 直接返回
    if (!alarmConfig.isAutoIncEmergency()) {
        return new ExecuteHelper(SimpleExecuteFactory.
          getExecute(alarmConfig.getAlarmLevel()),
          alarmConfig.getUsers());
    }


    // 報警等級開啟上升之趨勢
    // 1. 獲取設定的預設等級
    // 2. 判斷當前的報警次數, 選擇對應的報警型別
    // 3. 選擇具體的報警型別
    String defaultLevel = alarmConfig.getAlarmLevel();
    String selectLevel = null;
    List<String> selectUser = alarmConfig.getUsers();

    List<AlarmThreshold> list = alarmConfig.getAlarmThreshold();
    boolean useNew = false;
    boolean containDefaultLevel = false;
    for (AlarmThreshold alarmThreshold : list) {
        if (Objects.equals(alarmThreshold.getAlarmLevel(), defaultLevel)) {
            containDefaultLevel = true;
        }
    }


    for (AlarmThreshold alarmThreshold : list) {
        // 表示當前的報警等級已經趕上預設的報警等級了, 所以要選擇新的報警型別
        if (Objects.equals(alarmThreshold.getAlarmLevel(), defaultLevel)) {
            useNew = true;
        }

        if (count < alarmThreshold.getThreshold()) {
            break;
        }

        selectLevel = alarmThreshold.getAlarmLevel();
        // 選擇新的報警型別時, 需要更新報警使用者
        selectUser = alarmThreshold.getUsers(); 
    }


    // 閥值列表中不包含預設報警型別,則根據新的來
    if (!containDefaultLevel && selectLevel != null) {
        return new ExecuteHelper(SimpleExecuteFactory.getExecute(selectLevel), selectUser);
    }


    // 如果閥值列表中包含了預設報警型別, 且已經超過預設閥值
    if (useNew && selectLevel != null) {
        return new ExecuteHelper(SimpleExecuteFactory.getExecute(selectLevel), selectUser);
    } else {
        return new ExecuteHelper(SimpleExecuteFactory.getExecute(defaultLevel), alarmConfig.getUsers());
    }
}
複製程式碼

具體的實現基本和我們前面分析的一樣,但有一個地方需要額外注意

  • 預設報警閥值,可以直接決定是否需要報警
  • 因此定義的其他報警方式的閥值,應該在預設的閥值區間內
  • 當然AlarmThreshold中不包含預設報警方式時,優先選擇閥值區間的報警方式
  • 當然AlarmThreshold中包含預設報警方式時,根據新的規則做處理

(吐槽:上面這個實現有點繞,後面想辦法規避下,搞得不太好理解了)

另外一個問題就是,上面的實現沒有支援可以同時選擇多個報警執行器的情況

因為考慮到後面肯定會對報警規則的定義和解析放開,所以先實現了一個簡單的場景,具體的放在後面處理

III. 小結

到這裡報警規則和報警執行器之間的解析關係已確定,剩下的東西就簡單了,一個維持報警頻率計數,一個報警執行緒池,再加上一個對外介面的封裝而言

基本上,到這裡主要的核心邏輯已經完成,小結一下本系統中的核心設計理念 -- 一切可自定義(當然目前差得有點遠)

1. 報警執行器

  • 通過SPI機制支援使用者自定義擴充套件
  • 要求 Executor 擁有唯一標識
  • 因為報警執行器支援擴充套件,所以Executor的內部實現,完全可以由使用者決定

2. 報警規則

  • 目前報警規則只提供預設的基於頻率區間的選擇方案
  • 報警規則通過報警執行器的name與之唯一對應,若對應不上,則選擇預設執行器
  • 報警規則的載入同樣基於SPI,支援自定義,因此報警規則可以存在任何地方
  • 報警規則載入器,提供一個報警規則變動的鉤子(load()),若採用自定義的載入類,則確保規則變動時,主動回撥這個方法
  • 預設的報警規則載入類,是基於系統的配置檔案實現,內部託管了檔案的變動更新事件(使用commons-io實現)

IV. 其他

相關博文

  1. 報警系統QuickAlarm總綱
  2. 報警系統QuickAlarm之報警執行器的設計與實現
  3. 報警系統QuickAlarm之報警規則的設定與載入
  4. 報警系統QuickAlarm之報警規則解析
  5. 報警系統QuickAlarm之頻率統計及介面封裝
  6. 報警系統QuickAlarm使用手冊

專案: QuickAlarm

個人部落格: Z+|blog

基於hexo + github pages搭建的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

宣告

盡信書則不如,已上內容,純屬一家之言,因本人能力一般,見識有限,如發現bug或者有更好的建議,隨時歡迎批評指正,我的微博地址: 小灰灰Blog

掃描關注

QrCode

相關文章