5000 台 IoT 设备的异常检测,全 Serverless 架构月成本几十美元——Lambda + DynamoDB + Step Functions 实战

5000 台 IoT 设备的异常检测,全 Serverless 架构月成本几十美元------Lambda + DynamoDB + Step Functions 实战

管几千台 IoT 设备,传统方案要自己搭服务器跑定时脚本?亚马逊云科技官博最近分享了一个纯 Serverless 方案,分钟级检测、告警去重、批量故障识别,5000 台设备月成本可能就几十美元。

亚马逊云科技官博最近(3/27)发了一篇实战文章:用纯 Serverless 架构搭了一套分钟级的 IoT 设备异常检测系统。不用管服务器,按量付费,几千台设备的规模成本还很低。

拆解一下核心设计思路和代码实现。

业务痛点

智能建筑的 IoT 设备管理,几个头疼的问题:

  1. 设备多、类型杂:一栋楼几百台设备,温控、门禁、电梯、消防,每种心跳周期不一样
  2. 误报太多:设备实际在线但平台显示离线,维保人员收到一堆无效告警,干脆关了邮件通知
  3. 批量故障定位慢:网络断了导致一层楼的设备全掉线,告警风暴来了才发现是网络问题不是设备问题
  4. 告警重复:同一台设备已经报过异常了,下一次检测又报一遍

这些问题用传统方案(轮询数据库、定时脚本)都能解决,但维护成本高,扩展性差。

架构设计

整套系统全部 Serverless,核心组件:

arduino 复制代码
IoT 设备 → MQTT → Amazon IoT Core → IoT Rules → Lambda(数据预处理)
                                                        ↓
                                                  DynamoDB(设备状态表)
                                                        ↓
                                    EventBridge Scheduler(每分钟触发)
                                                        ↓
                                              Step Functions(检测流程编排)
                                                        ↓
                                    Lambda(异常判定) → SNS(告警通知)

核心表设计

Amazon DynamoDB 里维护一张设备状态表:

python 复制代码
# DynamoDB 表结构
{
    "deviceId": "00284L4XHV5Y8T0601001142U",  # PK
    "projectId": "002",                         # GSI
    "lastHeartbeat": 1766743982151,             # 最后心跳时间戳
    "lastDataReport": 1766743982151,            # 最后数据上报时间戳
    "status": "online",                          # 当前状态
    "alertSent": False,                          # 是否已发告警(防重复)
    "deviceType": "temperature_sensor",
    "heartbeatInterval": 300,                    # 预期心跳间隔(秒)
    "floor": "3F",
    "room": "0301"
}

关键字段是 alertSent------设备已经报过异常后置为 True,直到收到新心跳才重置。解决告警重复问题。

数据预处理 Lambda

设备上报数据时,Lambda 更新 DynamoDB:

python 复制代码
import boto3
import time

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('DeviceStatus')

def handler(event, context):
    """处理 IoT Core 转发的设备消息"""
    for record in event.get('records', [event]):
        device_id = record['deviceId']
        message_type = record.get('messageType', 'DATA')
        
        update_expr = "SET lastDataReport = :ts, #s = :online, alertSent = :false"
        expr_values = {
            ':ts': int(time.time() * 1000),
            ':online': 'online',
            ':false': False
        }
        
        if message_type == 'HEARTBEAT':
            update_expr += ", lastHeartbeat = :ts"
        
        table.update_item(
            Key={'deviceId': device_id},
            UpdateExpression=update_expr,
            ExpressionAttributeNames={'#s': 'status'},
            ExpressionAttributeValues=expr_values
        )
    
    return {'statusCode': 200}

异常检测逻辑

每分钟 Amazon EventBridge Scheduler 触发 AWS Step Functions,遍历设备表检查异常:

python 复制代码
import boto3
import time

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('DeviceStatus')
sns = boto3.client('sns')

ALERT_TOPIC = 'arn:aws:sns:ap-northeast-1:123456789:device-alerts'

def detect_anomalies(event, context):
    """检测设备异常"""
    now = int(time.time() * 1000)
    anomalies = []
    
    # 扫描所有在线设备
    response = table.scan(
        FilterExpression='#s = :online AND alertSent = :false',
        ExpressionAttributeNames={'#s': 'status'},
        ExpressionAttributeValues={':online': 'online', ':false': False}
    )
    
    for device in response['Items']:
        heartbeat_interval = device.get('heartbeatInterval', 300) * 1000
        # 超过 2 倍心跳间隔未收到消息,判定异常
        threshold = heartbeat_interval * 2
        
        last_contact = max(
            device.get('lastHeartbeat', 0),
            device.get('lastDataReport', 0)
        )
        
        if now - last_contact > threshold:
            anomalies.append({
                'deviceId': device['deviceId'],
                'projectId': device['projectId'],
                'floor': device.get('floor'),
                'lastContact': last_contact,
                'silentMinutes': (now - last_contact) // 60000
            })
            
            # 标记已告警,防止重复
            table.update_item(
                Key={'deviceId': device['deviceId']},
                UpdateExpression='SET alertSent = :true, #s = :offline',
                ExpressionAttributeNames={'#s': 'status'},
                ExpressionAttributeValues={':true': True, ':offline': 'offline'}
            )
    
    # 批量故障检测:同一楼层超过 3 台设备同时离线
    floor_counts = {}
    for a in anomalies:
        key = f"{a['projectId']}-{a.get('floor', 'unknown')}"
        floor_counts[key] = floor_counts.get(key, 0) + 1
    
    batch_failures = {k: v for k, v in floor_counts.items() if v >= 3}
    
    if anomalies:
        alert_message = format_alert(anomalies, batch_failures)
        sns.publish(TopicArn=ALERT_TOPIC, Message=alert_message)
    
    return {
        'totalChecked': response['Count'],
        'anomaliesFound': len(anomalies),
        'batchFailures': batch_failures
    }

def format_alert(anomalies, batch_failures):
    """格式化告警消息"""
    lines = [f"检测到 {len(anomalies)} 台设备异常\n"]
    
    if batch_failures:
        lines.append("⚠ 疑似批量故障:")
        for location, count in batch_failures.items():
            lines.append(f"  {location}: {count} 台设备同时离线(可能是网络问题)")
        lines.append("")
    
    for a in anomalies[:10]:  # 最多显示 10 台
        lines.append(
            f"- {a['deviceId']} | {a.get('floor','?')} | "
            f"已静默 {a['silentMinutes']} 分钟"
        )
    
    if len(anomalies) > 10:
        lines.append(f"... 还有 {len(anomalies) - 10} 台")
    
    return '\n'.join(lines)

Step Functions 编排

用 AWS Step Functions 编排整个检测流程,好处是可以加重试、超时、并行处理:

json 复制代码
{
  "StartAt": "DetectAnomalies",
  "States": {
    "DetectAnomalies": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789:function:detect-anomalies",
      "Next": "CheckResults"
    },
    "CheckResults": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.anomaliesFound",
          "NumericGreaterThan": 0,
          "Next": "LogToCloudWatch"
        }
      ],
      "Default": "Done"
    },
    "LogToCloudWatch": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789:function:log-anomalies",
      "Next": "Done"
    },
    "Done": {
      "Type": "Succeed"
    }
  }
}

几个设计亮点

1. 分钟级而不是秒级

选择分钟级检测而不是秒级,是成本和实时性的权衡。IoT 设备的心跳周期通常是 5 分钟,1 分钟检测一次已经够用。秒级检测的 Lambda 调用量会高 60 倍,成本完全不值。

2. 告警去重

alertSent 字段 + DynamoDB 条件更新,确保同一台设备在恢复前只告警一次。收到新心跳后重置标志,进入下一轮检测。

3. 批量故障识别

不只是逐台设备告警,还聚合分析------同一楼层 3 台以上设备同时离线,大概率是网络问题不是设备问题。告警消息里直接说明"疑似网络故障",维保人员不用逐台排查。

4. 全 Serverless 成本

组件 月估算(5000 台设备)
IoT Core MQTT 消息量 × 单价
Lambda(数据预处理) ~150 万次调用/月
Lambda(异常检测) ~43200 次调用/月(每分钟一次)
DynamoDB 5000 条记录,按需模式
Step Functions ~43200 次执行/月
SNS 告警消息,量很小

5000 台设备的规模,全 Serverless 按需付费,成本比自建服务器跑定时脚本低很多,运维量也小得多。

和 OpenClaw 的结合

OpenClaw Agent 可以订阅 SNS 告警,自动处理:

bash 复制代码
# OpenClaw Skill 示例:IoT 告警自动处理
# 1. 收到告警 → 判断严重程度
# 2. 批量故障 → 自动查网络设备状态
# 3. 单设备离线 → 创建工单到维保系统
# 4. 误报 → 调整检测阈值

运维自动化的核心思路:把重复的判断和操作交给 Agent,人只处理需要决策的问题。

落地建议

  1. 先从心跳监控开始:最简单的异常检测,设备没心跳就告警
  2. 加数据异常检测:温度突然跳变、湿度持续为 0 这类数值异常
  3. 用 Step Functions 编排:方便后续加新的检测逻辑,不用改主流程
  4. 告警分级:单设备离线 → 邮件通知;批量故障 → 短信 + 电话

亚马逊云科技官博原文:aws.amazon.com/cn/blogs/ch... Amazon IoT Core:aws.amazon.com/cn/iot-core... AWS Step Functions:aws.amazon.com/cn/step-fun... Amazon DynamoDB:aws.amazon.com/cn/dynamodb... OpenClaw:github.com/openclaw/op...

相关推荐
赛博三把手2 天前
Claude Fable 5 API 调用完整指南:官方、AWS、OpenRouter 与中转平台对比(2026 最新)
网络·云计算·aws
Devlive 开源社区4 天前
一口气新增三家云——七牛云 Kodo、AWS S3、Cloudflare R2;并支持应用内自动检测与一键升级
云计算·aws
云服务器代理商5 天前
AWS Bedrock 指南:模型接入、Knowledge Base RAG、Agents 与计费方式
云计算·aws·亚马逊云服务器·aws bedrock·aws代理·亚马逊云代理商
望江东浪6 天前
Vector 选型与实战:vs OTel / Logstash / Fluentd 全维对比,及统一日志与指标管道的 AWS ECS 落地
云计算·aws
威联通网络存储11 天前
基于TS-h3087XU-RP的大型成套空分设备DCS历史趋势数据治理
aws
A小辣椒1 个月前
AWS Clould Support Engineer就职面试题
aws
亚林瓜子1 个月前
AWS WAF中如何放行某个触发了托管规则的接口
aws·waf
悠悠121381 个月前
AWS DevOps Agent 体验一周后,我决定把 oncall 手机调成静音了
云计算·aws·devops
yyuuuzz1 个月前
独立站运营的几个技术层面常见问题
大数据·运维·服务器·网络·数据库·aws
yyuuuzz1 个月前
游戏云服务器推荐的技术选择思路
大数据·运维·服务器·游戏·云计算·aws