前面將報警規則的制定載入解析,以及報警執行器的定義載入和擴充套件進行了講解,基本上核心的內容已經完結,接下來剩下內容就比較簡單了
- 報警頻率的統計
- 報警執行緒池
- 對外封裝統一可用的解耦
I. 報警頻率統計
1. 設計
前面在解析報警規則時,就有一個count引數,用來確定具體選擇什麼報警執行器的核心引數,我們維護的方法也比較簡單:
- 針對報警型別,進行計數統計,沒呼叫一次,則計數+1
- 每分鐘清零一次
2. 實現
因為每種報警型別,都維護一個獨立的計數器
定義一個map來儲存對應關係
private ConcurrentHashMap<String, AtomicInteger> alarmCountMap;
複製程式碼
每分鐘執行一次清零
// 每分鐘清零一把報警計數
ScheduledExecutorService scheduleExecutorService = Executors.newScheduledThreadPool(1);
scheduleExecutorService.scheduleAtFixedRate(() -> {
for (Map.Entry<String, AtomicInteger> entry : alarmCountMap.entrySet()) {
entry.getValue().set(0);
}
}, 0, 1, TimeUnit.MINUTES);
複製程式碼
注意上面的實現,就有什麼問題?
有沒有可能因為map中的資料過大(或者gc什麼原因),導致每次清零花不少的時間,而導致計數不準呢? (先不給出回答)
計數加1操作
/**
* 執行緒安全的獲取報警總數 並自動加1
*
* @param key
* @return
*/
private int getAlarmCount(String key) {
if (!alarmCountMap.containsKey(key)) {
synchronized (this) {
if (!alarmCountMap.containsKey(key)) {
alarmCountMap.put(key, new AtomicInteger(0));
}
}
}
return alarmCountMap.get(key).addAndGet(1);
}
複製程式碼
II. 報警執行緒池
目前也只是提供了一個非常簡單的執行緒池實現,後面的考慮是抽象一個基於forkjoin的併發框架來處理(主要是最近接觸到一個大神基於forkjoin寫的併發器元件挺厲害的,所以等我研究透了,山寨一個)
// 報警執行緒池
private ExecutorService alarmExecutorService = new ThreadPoolExecutor(3, 5, 60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10),
new DefaultThreadFactory("sms-sender"),
new ThreadPoolExecutor.CallerRunsPolicy());
複製程式碼
任務提交執行
private void doSend(final ExecuteHelper executeHelper,
final AlarmContent alarmContent) {
alarmExecutorService.execute(() ->
executeHelper.getIExecute().sendMsg(
executeHelper.getUsers(),
alarmContent.getTitle(),
alarmContent.getContent()));
}
複製程式碼
III. 介面封裝
這個就沒什麼好說的了
public void sendMsg(String key, String content) {
sendMsg(new AlarmContent(key, null, content));
}
public void sendMsg(String key, String title, String content) {
sendMsg(new AlarmContent(key, title, content));
}
/**
* 1. 獲取報警的配置項
* 2. 獲取當前報警的次數
* 3. 選擇適當的報警型別
* 4. 執行報警
* 5. 報警次數+1
*
* @param alarmContent
*/
private void sendMsg(AlarmContent alarmContent) {
try {
// get alarm config
AlarmConfig alarmConfig = confLoader.getAlarmConfig(alarmContent.key);
// get alarm count
int count = getAlarmCount(alarmContent.key);
alarmContent.setCount(count);
ExecuteHelper executeHelper;
if (confLoader.alarmEnable()) { // get alarm execute
executeHelper = AlarmExecuteSelector.getExecute(alarmConfig, count);
} else { // 報警關閉, 則走空報警流程, 將報警資訊寫入日誌檔案
executeHelper = AlarmExecuteSelector.getDefaultExecute();
}
// do send msg
doSend(executeHelper, alarmContent);
} catch (Exception e) {
logger.error("AlarmWrapper.sendMsg error! content:{}, e:{}", alarmContent, e);
}
}
複製程式碼
介面封裝完畢之後如何使用呢?
我們使用單例模式封裝了唯一對外使用的類AlarmWrapper,使用起來也比較簡單,下面就是一個測試case
@Test
public void sendMsg() throws InterruptedException {
String key = "NPE";
String title = "NPE異常";
String msg = "出現NPE異常了!!!";
AlarmWrapper.getInstance().sendMsg(key, title, msg); // 微信報警
// 不存在異常配置型別, 採用預設報警, 次數較小, 則直接部署出
AlarmWrapper.getInstance().sendMsg("zzz", "不存在xxx異常配置", "報警嗒嗒嗒嗒");
Thread.sleep(1000);
}
複製程式碼
使用起來比較簡單,就那麼一行即可,從這個使用也可以知道,整個初始化,就是在這個物件首次被訪問時進行
建構函式內容如下:
private AlarmWrapper() {
// 記錄每種異常的報警數
alarmCountMap = new ConcurrentHashMap<>();
// 載入報警配置資訊
confLoader = ConfLoaderFactory.loader();
// 初始化執行緒池
initExecutorService();
}
複製程式碼
所有如果你希望在自己的應用使用之前就載入好所有的配置,不妨提前執行一下 AlarmWrapper.getInstance()
IV. 小結
基於此,整個系統設計基本上完成,當然程式碼層面也ok了,剩下的就是使用手冊了
再看一下我們的整個邏輯,基本上就是下面這個流程了
- 提交報警
- 封裝報警內容(報警型別,報警主題,報警內容)
- 維護報警計數(每分鐘計數清零,每個報警型別對應一個報警計數)
- 選擇報警
- 根據報警型別選擇報警規則
- 根據報警規則,和當前報警頻率選擇報警執行器
- 若不開啟區間對映,則返回預設執行器
- 否則遍歷所有執行器的報警頻率區間,選擇匹配的報警規則
- 執行報警
- 封裝報警任務,提交執行緒池
- 報警執行器內部實現具體報警邏輯
V. 其他
相關博文
- 報警系統QuickAlarm總綱
- 報警系統QuickAlarm之報警執行器的設計與實現
- 報警系統QuickAlarm之報警規則的設定與載入
- 報警系統QuickAlarm之報警規則解析
- 報警系統QuickAlarm之頻率統計及介面封裝
- 報警系統QuickAlarm使用手冊
專案: QuickAlarm
- 專案地址: Quick-Alarm
- 部落格地址: 小灰灰Blog
個人部落格: Z+|blog
基於hexo + github pages搭建的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
宣告
盡信書則不如,已上內容,純屬一家之言,因本人能力一般,見識有限,如發現bug或者有更好的建議,隨時歡迎批評指正,我的微博地址: 小灰灰Blog