一个物联网平台的告警系统设计:从告警风暴到精准通知

0 阅读6分钟

做物联网平台的,迟早会被一个问题折磨:告警太多。

设备一多,告警量就爆炸。运维人员每天收到几千条告警,看到后来直接麻木了,真的出了问题反而被淹没在告警海洋里。

这就是"告警风暴"。我最近参与了一个工业物联网平台的告警系统重构,从设计到落地踩了不少坑,分享一下。

image.png

一、告警风暴是怎么产生的

先看一个真实的场景。

某制造企业有300台设备,每台设备有5个传感器。每个传感器设了上下限阈值,超阈值就告警。

粗算一下:

  • 300台 × 5个传感器 = 1500个监控点
  • 每个监控点每天平均触发2次告警 = 3000条/天
  • 如果某台设备出了问题,可能导致关联传感器同时告警 → 一次故障可能产生几十条告警

3000条/天,平均每分钟2条。运维人员根本看不过来。

更糟糕的是"告警雪崩":一个核心交换机挂了,下面50台设备同时离线,瞬间产生几百条告警。运维人员的手机被轰炸,真正的根因反而被淹没了。

二、告警系统的架构设计

微信图片_20260904162042_1258_20.png

一个完整的告警系统,包含以下几个核心模块:

┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  数据采集层   │───▶│  规则引擎层   │───▶│  告警处理层   │
│              │    │              │    │              │
│ 传感器数据    │    │ 阈值规则      │    │ 告警收敛      │
│ 设备状态      │    │ 复合规则      │    │ 告警关联      │
│ 日志数据      │    │ AI模型       │    │ 告警升级      │
└──────────────┘    └──────────────┘    └──────┬───────┘
                                               │
                                        ┌──────▼───────┐
                                        │  通知分发层   │
                                        │              │
                                        │ 短信/电话     │
                                        │ 邮件/IM       │
                                        │ 工单系统      │
                                        └──────────────┘

2.1 规则引擎层

规则引擎是告警的"大脑",负责判断什么情况下需要告警。

05709c9d43954e18ab7fbd9a30da0a2e~tplv-obj.png

class AlarmRule:
    """告警规则基类"""
    def __init__(self, name, severity, condition):
        self.name = name
        self.severity = severity  # critical, warning, info
        self.condition = condition
        self.enabled = True
    
    def evaluate(self, context):
        """评估规则是否触发"""
        if not self.enabled:
            return None
        if self.condition.check(context):
            return AlarmEvent(
                rule_name=self.name,
                severity=self.severity,
                timestamp=datetime.now(),
                context=context
            )
        return None# 简单阈值规则
class ThresholdRule(AlarmRule):
    def __init__(self, metric, operator, threshold, **kwargs):
        super().__init__(**kwargs)
        self.metric = metric
        self.operator = operator
        self.threshold = threshold
    
    def evaluate(self, context):
        value = context.get_metric(self.metric)
        triggered = False
        if self.operator == '>':
            triggered = value > self.threshold
        elif self.operator == '<':
            triggered = value < self.threshold
        elif self.operator == '==':
            triggered = value == self.threshold
        
        if triggered:
            return AlarmEvent(
                rule_name=self.name,
                severity=self.severity,
                metric=self.metric,
                value=value,
                threshold=self.threshold,
                timestamp=datetime.now(),
                device_id=context.device_id
            )
        return None# 复合规则:多个条件同时满足
class CompositeRule(AlarmRule):
    def __init__(self, rules, logic='AND', **kwargs):
        super().__init__(**kwargs)
        self.rules = rules
        self.logic = logic
    
    def evaluate(self, context):
        results = [rule.evaluate(context) for rule in self.rules]
        if self.logic == 'AND':
            if all(r is not None for r in results):
                return AlarmEvent(
                    rule_name=self.name,
                    severity=self.severity,
                    sub_events=results,
                    timestamp=datetime.now()
                )
        elif self.logic == 'OR':
            triggered = [r for r in results if r is not None]
            if triggered:
                return AlarmEvent(
                    rule_name=self.name,
                    severity=self.severity,
                    sub_events=triggered,
                    timestamp=datetime.now()
                )
        return None# 趋势规则:基于变化趋势判断
class TrendRule(AlarmRule):
    def __init__(self, metric, window_size, trend_type, **kwargs):
        super().__init__(**kwargs)
        self.metric = metric
        self.window_size = window_size
        self.trend_type = trend_type  # 'rising', 'falling', 'volatile'
    
    def evaluate(self, context):
        history = context.get_metric_history(self.metric, self.window_size)
        if len(history) < self.window_size:
            return None
        
        if self.trend_type == 'rising':
            # 连续上升
            if all(history[i] < history[i+1] for i in range(len(history)-1)):
                return AlarmEvent(
                    rule_name=self.name,
                    severity=self.severity,
                    trend='rising',
                    values=history,
                    timestamp=datetime.now()
                )
        return None

2.2 告警处理层(核心:告警收敛)

微信图片_20260904165425_1263_20.png

这是解决告警风暴的关键。主要做三件事:

1. 告警去重

同一条告警在短时间内重复触发,只保留一条:

class AlarmDeduplicator:
    def __init__(self, dedup_window=300):  # 5分钟去重窗口
        self.dedup_window = dedup_window
        self.last_alarm = {}  # key -> timestamp
    
    def deduplicate(self, alarm):
        key = f"{alarm.device_id}:{alarm.rule_name}"
        now = time.time()
        
        if key in self.last_alarm:
            if now - self.last_alarm[key] < self.dedup_window:
                return None  # 重复告警,丢弃
        
        self.last_alarm[key] = now
        return alarm

2. 告警聚合

关联的告警合并成一条,避免告警雪崩:

class AlarmAggregator:
    def __init__(self, aggregation_window=60):
        self.aggregation_window = aggregation_window
        self.pending_alarms = []
    
    def aggregate(self, alarms):
        """按设备+时间窗口聚合告警"""
        groups = defaultdict(list)
        for alarm in alarms:
            # 同一设备、相近时间的告警归为一组
            group_key = f"{alarm.device_id}:{int(alarm.timestamp / self.aggregation_window)}"
            groups[group_key].append(alarm)
        
        aggregated = []
        for key, group in groups.items():
            if len(group) == 1:
                aggregated.append(group[0])
            else:
                # 合并成一条聚合告警
                parent = AlarmEvent(
                    rule_name='aggregated_alarm',
                    severity=max(g.severity for g in group),
                    device_id=group[0].device_id,
                    sub_events=group,
                    count=len(group),
                    timestamp=group[0].timestamp
                )
                aggregated.append(parent)
        
        return aggregated

3. 告警抑制

微信图片_20260904162322_1260_20.png

当根因告警触发时,抑制由它引起的下游告警:

class AlarmSuppressor:
    def __init__(self):
        # 定义告警之间的依赖关系
        self.dependencies = {
            'network_switch_down': [
                'device_offline',  # 交换机挂了 → 抑制下游设备离线告警
                'connection_timeout'
            ],
            'power_failure': [
                'device_offline',
                'sensor_no_data',
                'communication_error'
            ]
        }
    
    def suppress(self, alarms):
        """抑制被根因告警覆盖的下游告警"""
        active_suppressions = set()
        
        # 先找出根因告警
        for alarm in alarms:
            if alarm.rule_name in self.dependencies:
                active_suppressions.update(self.dependencies[alarm.rule_name])
        
        # 过滤掉被抑制的告警
        return [a for a in alarms if a.rule_name not in active_suppressions]

2.3 通知分发层

微信图片_20260904170052_1264_20.png

不同类型的告警,通知不同的对象,用不同的方式:

class NotificationDispatcher:
    def __init__(self):
        self.routes = {
            'critical': [SMSNotifier(), PhoneNotifier(), IMNotifier()],
            'warning': [IMNotifier(), EmailNotifier()],
            'info': [EmailNotifier()]
        }
        self.escalation_rules = {
            'critical': {
                'timeout': 300,  # 5分钟未处理
                'escalate_to': 'manager',
                'notify': SMSNotifier()
            }
        }
    
    def dispatch(self, alarm):
        notifiers = self.routes.get(alarm.severity, [])
        for notifier in notifiers:
            notifier.send(alarm)
        
        # 记录告警,用于升级判断
        self.alarm_tracker.track(alarm)
    
    def check_escalation(self):
        """检查是否有需要升级的告警"""
        for alarm in self.alarm_tracker.get_unhandled():
            rule = self.escalation_rules.get(alarm.severity)
            if rule and time.time() - alarm.timestamp > rule['timeout']:
                rule['notify'].send(
                    f"告警升级:{alarm.rule_name}{rule['timeout']}s 未处理"
                )

三、重构后的效果

经过三个多月的优化,效果还是比较明显的:

指标重构前重构后
日均告警量3000+200~300
有效告警占比~15%~85%
平均响应时间45分钟8分钟
告警风暴次数每周2~3次基本消除

关键改进点:

  • 去重:消除了60%的重复告警
  • 聚合:把关联告警合并,减少了25%的告警量
  • 抑制:根因定位更准确,下游告警不再干扰
  • 分级通知:重要告警能第一时间触达对的人

四、几点经验

  1. 告警不是越多越好。一条精准的告警,胜过一百条模糊的告警。宁可少报,不要滥报。
  2. 规则要持续优化。上线后要根据实际运行情况,不断调整阈值和规则。可以用历史数据做回测,找到最优阈值。
  3. 告警要有上下文。告警消息里要包含足够的上下文信息——当前值、阈值、历史趋势、影响范围,让运维人员一看就知道严重程度和处理方向。
  4. 要有告警升级机制。如果告警发出去没人处理,要能自动升级。不然告警系统就形同虚设。

这套告警系统底层用的是JVS物联网平台的规则引擎做的,省去了自己写规则匹配和事件处理的底层代码。规则引擎负责告警规则的解析和执行,上层自己去重、聚合、抑制的逻辑是我们自己写的。整体来说,规则引擎这部分如果自己做,至少要两三个月,用现成的省了不少事。


#物联网告警 #告警系统 #规则引擎 #告警收敛 #JVS物联网平台