背景:
見上一篇文章 https://www.cnblogs.com/langshiquan/p/9497464.html
我們在使用pinpoint的報警功能的時候,發現如果持續一段時間內一直存在異常情況,但是並不是每一分鍾都會接受到pinpoint的報警郵件,而是有一個時間間隔的,本文旨在分析其報警的策略算法。
相關類:
1)CheckResult,該類代表了報警歷史,是存儲在數據庫當中的。
該類與此算法有關的字段有3個,分別為detected,sequenceCount,timingCount。
detected代表是否報警過,sequenceCount代表已經進行檢查的次數,timingCount代表第幾次檢查應該報警。
2)AlarmChecker,該類代表了一次檢查的結果,是在內存中的臨時對象。
該類與此算法有關的字段有1個,分別為detected。
detected代表此次是否有異常情況
AlarmChecker的detected字段和CheckResult的detected字段含義稍有區別,請注意區分
算法步驟:
1.當AlarmChecker的detected字段為true 的時候(此次有異常情況),則根據isTurnToSendAlarm方法來判斷此次是否應該報警:
1)檢查CheckResult報警歷史中的detected來查看歷史上是否報警過,如果沒有,則返回true
2)如果detected == true,則代表歷史上報過警,sequenceCount和timingCount是否相差1,如果是則返回true,否則返回false
2.更新CheckResult記錄:
1)刪除原先的記錄
2)如果AlarmChecker.detected == false,則插入一條“新”記錄,detected為false,sequenceCount為0,timingCount為1。
3)如果AlarmChecker.detected == true,則插入一條“舊”記錄,detected為true,sequenceCount在原先的基礎上+1,timingCount如果和sequenceCount相等則在原先的基礎上乘2再加1,否則不變。
效果:
實現了報警延遲功能,持續異常情況下,第1分鍾報警,接下來,第3分鍾報警(間隔2分鍾),接下來,第7分鍾報警(距離前一次間隔4分鍾)....
源碼:[現在看看源碼會比較清晰了]
AlarmWriter
package com.navercorp.pinpoint.web.alarm;
import java.util.List;
import java.util.Map;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import com.navercorp.pinpoint.web.alarm.checker.AlarmChecker;
import com.navercorp.pinpoint.web.alarm.vo.CheckerResult;
import com.navercorp.pinpoint.web.service.AlarmService;
/**
* @author minwoo.jung
*/
public class AlarmWriter implements ItemWriter<AlarmChecker> {
@Autowired(required = false)
private AlarmMessageSender alarmMessageSender = new EmptyMessageSender();
@Autowired
private AlarmService alarmService;
@Override
public void write(List<? extends AlarmChecker> checkers) throws Exception {
Map<String, CheckerResult> beforeCheckerResults = alarmService.selectBeforeCheckerResults(checkers.get(0).getRule().getApplicationId());
for (AlarmChecker checker : checkers) {
CheckerResult beforeCheckerResult = beforeCheckerResults.get(checker.getRule().getCheckerName());
if (beforeCheckerResult == null) {
beforeCheckerResult = new CheckerResult(checker.getRule().getApplicationId(), checker.getRule().getCheckerName(), false, 0, 1);
}
if (checker.isDetected()) {
sendAlarmMessage(beforeCheckerResult, checker);
}
alarmService.updateBeforeCheckerResult(beforeCheckerResult, checker);
}
}
// 防止重復報警
private void sendAlarmMessage(CheckerResult beforeCheckerResult, AlarmChecker checker) {
if (isTurnToSendAlarm(beforeCheckerResult)) {
if (checker.isSMSSend()) {
alarmMessageSender.sendSms(checker, beforeCheckerResult.getSequenceCount() + 1);
}
if (checker.isEmailSend()) {
alarmMessageSender.sendEmail(checker, beforeCheckerResult.getSequenceCount() + 1);
}
}
}
private boolean isTurnToSendAlarm(CheckerResult beforeCheckerResult) {
// 之前沒報過警就報警
if (!beforeCheckerResult.isDetected()) {
return true;
}
// 如果之前報過警,則延遲報警;檢查sequenceCount和timingCount是否相差1。
int sequenceCount = beforeCheckerResult.getSequenceCount() + 1;
if (sequenceCount == beforeCheckerResult.getTimingCount()) {
return true;
}
return false;
}
}
alarmService.updateBeforeCheckerResult方法
@Override public void updateBeforeCheckerResult(CheckerResult beforeCheckerResult, AlarmChecker checker) { alarmDao.deleteCheckerResult(beforeCheckerResult); if (checker.isDetected()) { beforeCheckerResult.setDetected(true); // 更新下次應該報警的時間點 beforeCheckerResult.increseCount(); alarmDao.insertCheckerResult(beforeCheckerResult); } else { alarmDao.insertCheckerResult(new CheckerResult(checker.getRule().getApplicationId(), checker.getRule().getCheckerName(), false, 0, 1)); } }
beforeCheckerResult.increseCount()方法
// 延時報警,防止每分鍾都報警,引起轟炸 public void increseCount() { // sequenceCount為檢查的次數 ++sequenceCount; // timingCount代表檢查次數達到timingCount則報警 // 如果此次已經報警,則延遲下次報警的時間 if (sequenceCount == timingCount) { timingCount = sequenceCount * 2 + 1; } }
